Teams, git, and CI
How to run celigo pull, status, diff, push, and lint as a team: one repository, feature branches, pull-request review, a deploy job. The local tree documents the tree itself; this page is the recommended setup, with the reason wherever a rule is not obvious.
Pick a mode, one per repository
A standard tree is faithful to one account: files carry their _ids and every reference is a raw id. It is the right shape for one environment per repository (or per directory), and it is what this page assumes. A neutral tree carries no ids in its tracked files, so one permanent git branch per environment merges cleanly. The two have opposite commit rules for .celigo/, so pick one mode per repository and say which in its README.
Repository layout
One repository per integration (a scoped tree, --integration <id>) or per team (an account tree). Scoped where you can: a scoped tree is smaller, pulls faster, and never writes into another integration unless you pass --allow-dependencies.
repo/
.celigo/manifest.json COMMIT — the shared optimistic-lock tokens and file hashes
.celigo/.gitignore written by pull: local/, tmp/, bindings.json, *.tmp
.celigo/local/ never committed — per-machine state (last pull, lint feedback, the tree lock)
.celigo/storage/*.json commit (storage metadata, no secrets) — or .celigoignore storage
.celigo/targets/ commit — one file per promote target
.gitattributes written by pull once: *.json/*.js/*.md text eol=lf;
.celigo/manifest.json -diff linguist-generated merge=binary
.celigoignore optional, commit: what this repository must never push
connections/ exports/ imports/ scripts/ integrations/<Int>/… commit.celigo/manifest.json
commit — the team's shared lock tokens and hashes
never (per-environment ids)
.celigo/.gitignore
written by pull: local/, tmp/, bindings.json, *.tmp
written by pull: *
.celigo/local/
never — per-machine state
never
.celigo/bindings.json
(absent)
never — this environment's credential ids
.celigo/storage/*.json
commit, or .celigoignore storage
commit
.celigo/targets/<name>.json
commit
(promote does not apply)
.gitattributes
written by pull once, at the tree root
same
.celigoignore
optional, commit
optional, commit
resource folders
commit
commit
The rule of thumb: anything two clones at the same commit would compute differently is per-machine and lives under .celigo/local/. The manifest is the shared half of the tree's state — a teammate's push is what refreshes the lock tokens for everyone — so it is committed, and it is deterministic so that a no-op pull leaves the checkout clean.
First pull of a git-tracked tree:
--no-symlinks writes .link.json pointer files instead of symlinks. The style is sticky, and pointer files survive every platform's checkout — a Windows checkout of a symlink is a text file. The pull also writes the .gitattributes above: the tree's text files stay LF on every platform, and the manifest merges as binary, so git stops on a manifest conflict instead of interleaving two JSON documents. A CRLF checkout is not an edit either way — change detection compares .json, .js, and .md files line-ending-insensitively. Detection only: a script edited in a CRLF checkout is pushed with its \r\n, so keep the .gitattributes (or core.autocrlf=input) wherever scripts are edited on Windows.
Day to day
Bind your shell to the account's profile once, instead of passing --profile on every command:
CELIGO_PROFILE is the per-session binding. celigo profile use is machine-wide and redirects every other terminal and agent session on the machine. Neither is what binds the tree to an account — that is the manifest, checked by pull, diff, and push however the profile was selected.
The account binding
The manifest records two facts about the account a tree was pulled from: the API host (baseUrl) and the token owner's user id (accountUserId, from GET /v1/tokenInfo). Every pull into an existing tree, every diff, and every push checks both; push --dry-run checks the host offline and, whenever a token is configured and the plan has items, runs the same identity check as the push, before its first plan line. The verdicts, in order:
targets another host (api.eu…, a forgotten CELIGO_BASE_URL defaulting to production)
Refused before any request: This tree is bound to <baseUrl> (account user <id>) but profile '<p>' targets <url> — refusing to push to a different account …
same host; tokenInfo answers with a user id that differs from the manifest's
Refused before anything of that account is read or written: This tree is bound to account user <id> but the token of profile '<p>' belongs to <other> …
same host; tokenInfo answers with the manifest's user id
Allowed. A different profile name adds note: same account — the token of profile '<p>' belongs to this tree's account user (<id>).
same host; the tree never recorded a user id (pulled while the probe was failing); the selected profile is the one the tree was pulled with
Allowed and bound: the id is recorded and checked from then on, announced as note: bound this tree to account user <id> …. diff never binds — it writes nothing.
same host; the tree never recorded a user id; the selected profile is a different profile
push and push --dry-run are refused — refusing to push: a stale --profile for another account on <host> looks exactly like this — naming two exits: --allow-account-mismatch to bind the tree to this account (then commit the bound manifest so CI needs no flag), or a re-pull with the recorded profile. pull and diff warn and continue on the host match, unbound.
same host; tokenInfo fails (a known platform issue makes it return 500 Internal Server Error for some production tokens)
Allowed on the host match alone: warning: same host (account identity could not be verified — GET /v1/tokenInfo failed: …); relying on the host match (<host>).
So the binding detects a wrong host with certainty, and a wrong account on the same host whenever the token's tokenInfo works. It cannot detect a wrong account on the same host for a token whose tokenInfo fails; there, updates still fail harmlessly (the lock GET finds no such resource), but a create would land in the other account, and --force-remote skips the lock GET. The probe runs on every binding command and nothing about a failed probe is persisted, so the check takes effect the day the endpoint answers. Until then, for production tokens: read the warning, keep one profile per shell (CELIGO_PROFILE), and prefer env-only CI — one token, one base URL, no profile to get stale — for anything that writes.
--allow-account-mismatch on pull and push is the only override (none on diff). It prints the target it is about to touch, and binding under it also re-records the host.
After a push, the file is the server's copy
Updates and creates alike write the server's response back and record its hash in the manifest, so status is clean because the tree is server-identical, a typed secret is masked again immediately, and the pull right after a push is a no-op. Two edits the tree cannot carry: _registeredConnectionIds and _registeredLookupCacheIds are ignored by PUT (use celigo integrations register-connections / register-lookup-caches; push reports the drop as note: … server dropped …), and an Integration App's documents are pull-only (celigo integrations persist-settings is the write path for its settings).
Secrets. Type the real value in place of "******", push, and the write-back masks it again. Between the edit and the push the plaintext is on disk — do not commit in between. celigo diff renders it as ****** → (set), in JSON output too.
Branches, review, rollback
main mirrors the account. Work happens on feature branches; the pull request is the review; a merge deploys.
Feature commits touch resource files only. The manifest changes in exactly two places: the deploy job's write-back commit after a
celigo push, and a release manager's "sync" commit after aceligo pull(drift from the UI, a rename, a resource created there). Keeping the manifest out of feature commits keepsgit revert <feature>a valid rollback and keeps merges off the token lines.The diff is the review artifact. Files are canonical JSON — stable key order, volatile fields stripped — so a two-edit PR is two files with a few changed lines. For "what will this do to the account", post
celigo push --dry-runandceligo diff --exit-codeoutput as the PR comment.diffwith no refs compares only the files the PR touched; to check specific resources against the account, name them.Rollback = revert the feature, then deploy.
git revert <merge>and let the deploy job push it, or by hand:git checkout <good> -- exports imports integrations scripts connections && celigo push. A content checkout, never a wholesalegit checkout <old>: that also rewinds the manifest, every hash then matches, andstatusandpushsee nothing to do while the server differs.Merge conflicts on the manifest are resolved the same way every time: take one side, then run
celigo pull— the manifest is derived state, and a pull rebuilds the hashes and tokens for whatever the tree holds. A half-merged resource file — a JSON document, ascript.js, aninstructions.md— is named as such by every verb, with the line of the first conflict marker;lint,push --dry-run, anddifffail on it rather than skipping it. A half-merged.link.jsonpointer sends nothing and is rewritten by the next pull, butstatuslists it asunreadable.
One writer per tree at a time
The committed manifest is a lock across time: a push from a checkout whose tokens are stale — because someone else pushed and committed the write-back since — is refused per file (remote changed since pull …), and after merging their write-back commit it succeeds. It is not a lock across simultaneous pushes from the same baseline: the platform has no conditional write, so two clones pushing the same edits at the same moment can both report updated while one silently loses. Push re-reads each document once after its PUT and, when the server already holds something else, says so (warning: <path> was overwritten by another writer right after this push …) and records the server's hash so status shows the file modified — that catches a writer landing inside the PUT→GET gap, not one landing later. Therefore:
Deploy from CI, not from laptops, for any tree more than one person edits; the deploy job's
concurrencygroup below serializes it.Within one clone, two concurrent
pull/pushruns are refused by the tree lock (.celigo/local/lock); a lock whose process is gone, or older than six hours, is taken over automatically, and--force-lockis the escape for a run that died on another machine sharing the directory.Interrupting a push is safe: updates and deletes are idempotent, and creates record their intent before the
POST, so the next run adopts them instead of duplicating them.The same new files pushed from two clones are the case no intent record can see, so push asks the server immediately before each
POST: an untracked namesake holding exactly this document, or one in the same integration, refuses the create (… another clone of this tree pushed the same file? Run celigo pull to track it, then remove the draft …). The residual window is two clones running the same push in lockstep. It is a mitigation, not a lock — deploy from CI, where theconcurrencygroup is the lock.
CI: validate on pull requests, deploy on merge
Both jobs are env-only: a token plus CELIGO_BASE_URL is a complete identity, with no config file and no profile add, because the tree is bound to the account and not to a profile name. The PR check needs a read-mode token; the deploy needs a full-mode one. The CLI detects CI (CI, GITHUB_ACTIONS, GITLAB_CI, JENKINS_URL, CIRCLECI, BUILDKITE, …) and skips its background tasks — the update check, the skills install, and the lint schema refresh.
⚠️ Store tokens in the CI secret store. Never commit a token or pass
--tokenon a command line that gets logged. Both token types from Resources → API tokens work; a personal access token carries its user's full permissions and expires after 90 days by default, so for CI prefer an account-level API token with a custom scope.
The deploy job must commit the manifest write-back: a successful push refreshes the lock tokens, and without that commit the next deploy touching the same resource is rejected as remote changed since pull.
Every write prints celigo: profile 'default' → api.integrator.io on stderr first, so the target sits in the job log next to the write, and the push refuses outright if the token's account is not the manifest's. Branch protection that blocks the write-back push: run the deploy from a bot account allowed to bypass it, or protect a release branch and deploy from main. Pass --dir <path> on every command when the tree is not the repository root. Without --prune, nothing is ever deleted and no confirmation prompt is triggered; updates whose server copy changed since the pull are rejected — re-pull and re-commit the tree rather than passing --force-remote in CI.
A tree pulled while the identity probe was failing has no recorded user id, and only the profile it was pulled with can bind it. CI's built-in default profile is a different name, so on such a tree a CI push continues on the host match while the probe keeps failing — and refuses the day the probe answers. Bind it once from a developer machine with the pulling profile (celigo pull, then commit .celigo/manifest.json) or, deliberately, with celigo push --allow-account-mismatch in the job, and commit the bound manifest so the flag can come off.
Which gates are usable today
celigo lint
yes
Errors: the guards a create introduces, invalid or half-merged files, the responseMapping wrapper.
celigo lint --strict
not yet
The published schemas are stricter than the API on real data — a pristine pull of a real account produces warnings, so --strict fails everything until the specs catch up.
celigo push --dry-run
yes
Exit 1 when any planned item is refused; 0 when changes are merely withheld.
celigo status --exit-code
yes
Git-style: informational without the flag.
celigo diff --exit-code
yes
Git-style: informational without the flag.
Promote on merge: two tokens
A deploy job that promotes needs the target's full-mode token and nothing else — the tree's source token is not used. The two identities have two names, and the CLI keeps them apart by variable:
CELIGO_API_TOKEN, CELIGO_BASE_URL
The source session: what pull, diff, and push use. Promote itself never uses them, but a push --dry-run gate in the same job does.
CELIGO_TARGET_API_TOKEN, CELIGO_TARGET_BASE_URL
The target that --to names, on a runner with no profile of that name. CELIGO_TARGET_BASE_URL defaults to https://api.integrator.io. A note: says the variable stands in.
CELIGO_CONCURRENCY
Requests in flight at once for pull, diff, push, and promote (1–16, default 8).
A job that sets only CELIGO_API_TOKEN and runs promote --to production is refused (target profile 'production' has no API token on this machine and CELIGO_TARGET_API_TOKEN is unset …) rather than writing the tree back into the source account under the target's name. --to production resolves to the committed .celigo/targets/production.json first — its recorded profile name is a label, its recorded host and account user are what is verified — and, unlike push, a target already bound to an account user is refused when the identity probe fails, not carried on the host match.
Two-stage pipelines (merge → sandbox, tag → production) are two such jobs with two target files and two secrets. The PR check stays as above against the source, plus celigo promote --to sandbox --dry-run or celigo diff --target sandbox when the sandbox token is available to the PR job as CELIGO_TARGET_API_TOKEN — the two pairs sit side by side in one job without crossing. Keep one writer per target: promote holds the tree lock like push, and the workflow-level concurrency key keeps two merges from racing.
Related
The local tree — the tree, its layout, what it never carries.
Push changes — the plan, the lock, the write-back.
Promote to another account — the target file and the first promote into an existing account.
Neutral trees — one branch per environment.
Scripting patterns and Configuration reference — the environment variables and CI recipes for the rest of the CLI.
Last updated
Was this helpful?