For the complete documentation index, see llms.txt. This page is also available as Markdown.

Push changes

celigo push writes a local tree's changes back to the account: a PUT for each modified document, a create for each untracked file that has no _id, and — only with --prune — a delete for each tracked file you removed. It is plan-first: the change set prints before anything is written, --dry-run stops there, and every guard the push applies runs while the plan is built, so the plan is the truth about what will be sent.

Required: full mode, --dry-run included. Every push names its target first on stderr: celigo: profile 'acme-prod' → api.integrator.io.

Signature

celigo push [refs...] [flags]

Arguments

Argument
Type
Required
Description

[refs...]

file path, link path, type:id, or bare id

No

Narrow the push to these resources. Without refs, every local change is planned.

Flags

Flag
Type
Default
Description

--dir <path>

string

.

Tree root directory.

--dry-run

boolean

false

Compute and print the plan without writing anything. Exits 1 when any item is refused.

--prune

boolean

false

Delete resources on the server whose local files were removed. Asks for confirmation.

--allow-dependencies

boolean

false

Also push resources owned by an integration outside this tree's scope (pulled as read-only context by default).

--force-remote

boolean

false

Skip the remote-change (lastModified) check and overwrite the server copy.

--no-create

boolean

false

Refuse to create resources for new files — a CI safety. --dry-run --no-create counts them as refused and exits 1.

--allow-connector-resources

boolean

false

Also push edited Integration App flows, exports, and imports. An app's integration.json is never pushable.

--allow-account-mismatch

boolean

false

Push to a different account than the tree is bound to (host or token owner) anyway.

-y, --yes

boolean

false

Skip the delete confirmation prompt.

--concurrency <n>

integer

8

Requests in flight at once (1–16). CELIGO_CONCURRENCY sets the default. Creates still run in dependency order, deletes dependents-first.

--force-lock

boolean

false

Take the tree lock even if another pull or push appears to hold it.

--no-verify

boolean

false

Skip the one GET after each update that detects another writer overwriting it right behind the PUT.

--allow-duplicate-names

boolean

false

Create a resource even when an untracked one of the same name already exists on the server. Skips the pre-POST namesake listing.

Example

The plan

The plan has three kinds of line. Items that will be sent (create, update, delete); refused: <path> — <reason> for every item the real push would fail on; and withheld: <path> — <reason> for what push leaves alone by design, with the flag that would send it.

A refused create also blocks everything that depends on it, transitively, so a refused create never leaves half a graph behind:

--dry-run then prints Dry run — nothing was written; N item(s) refused (exit 1): <paths> and exits 1, so a CI check cannot approve a half-merged tree or a payload the push would refuse; --format json carries the reason as items[].problem. Withholding is not a failure: a plan that is only withholdings ends with Nothing to push — N change(s) withheld (see above). and exits 0.

What push refuses

These refusals have no override. Fix the file, or re-pull it.

Refusal
Why

A document whose _id is not the tracked resource's — a copied, symlinked, or overwritten file.

The server ignores a mismatched body _id and would overwrite the other resource.

A connections, iclients, or mcp-oauth-providers document still holding "******".

A PUT with the mask would destroy the credential. Type the real value, use the group's set for its safe fields, or re-pull.

The responseMapping object wrapper — fields or lists as an object instead of an array — on a create or update, at any depth (routers[].branches[].pageProcessors[] included), and fields: null. On a create, the post-corruption fields: [null] and [{}] too.

The platform accepts the shape, answers 200 OK, and erases the step's mappings. celigo lint reports it as an error for the same reason.

A Celigo Storage item.

Storage is pull-only; the celigo storage commands write it.

A renamed script file without --prune.

Ambiguous between a rename and a delete plus a create. A script is renamed by its name field in script.json.

A create of a connection, iClient, stack, or on-premise agent in a neutral tree.

Per-environment resources are created with their own commands, then pulled.

These refusals name their exit in the message:

  • A file that is not a JSON object, and a script.js or instructions.md holding git conflict markers: <path>: unresolved git merge conflict (conflict markers at line N). Text has no parser to trip over <<<<<<<, so the scan is explicit — otherwise a half-merged script would be pushed as the script's code.

  • A vanished companion file — a deleted instructions.md or script.js. A PUT that omits the prompt would clear it; celigo pull restores the file.

  • A document outside a scoped tree's integration. See Scoped trees; --allow-dependencies overrides.

  • A create claiming an Integration App's _connectorId.

  • A new file whose folder push cannot honor — an unknown flow or API group ('<Group>' does not match a flow group of this integration — create the group first (flowGroupings on the integration), or move the file to flows/), a folder no tracked integration owns, an instances/ folder with no tracked parent, a file nested too deep, or a grouped flow under an integration created in the same push.

  • A reference that resolves to nothing: unresolved reference(s): <pointer> → <type> '<id>' (the referenced resource is neither tracked in this tree nor created in this push), or, when push <refs> left the new file out of the plan, … is a new file that this push's refs left out — add it to the refs, or push without refs.

  • A --prune delete whose server copy changed since the pull (see Prune deletes).

  • A new resource whose untracked namesake already exists on the server (see Creates); --allow-duplicate-names overrides.

What push withholds

Withheld items are left alone by design and do not fail the push:

  • Resources another integration owns, pulled into a scoped tree as reference context. --allow-dependencies sends them — edits and --prune deletes alike, so removing a borrowed folder from a checkout never deletes the owner's documents without the flag.

  • Integration App documents (_connectorId). Edits are withheld, --prune never deletes them, and an app's integration.json is never pushed — celigo integrations persist-settings is its write path. --allow-connector-resources pushes edited app flows, exports, and imports, with _connectorId stripped.

Creates

An untracked file with no _id is a create. Author it as <typedir>/<Name>/<doc>.json (folder form) or <typedir>/<Name>.json (quick form); the write-back normalizes to the folder form. A new script is a .js dropped in scripts/ — scripts/<Name>.js, or scripts/<Name>/script.js with an optional script.json beside it.

New files reference each other by relative path. A placeholder names the file of a tracked resource or of another new file in the same push, at any id-bearing position:

Push resolves the placeholders to server ids and runs creates in dependency rounds — a round is every remaining create whose dependencies have been attempted — so an import is never POSTed before its export's id exists. The order is derived from the documents on every push and never stored, which is what lets a git merge that adds a flow and its new exports push correctly. A reference cycle (a script that calls the export that hooks it, an integration alias naming one of its own flows) is created without the deferred half and repaired by a disclosed fixup PUT; a cycle within one tier is a hard error naming the nodes.

Location decides ownership. A flow, sync, tool, or builder-mode API authored inside integrations/<Name>/… inherits _integrationId from its location — plus _flowGroupingId in a flow-group subfolder, _apiGroupingId in an API-group subfolder, _abstractFlowId in an instances/ folder — disclosed as a side effect. An explicit field in the document always wins. What the platform cannot own inherits nothing: a connection, a script-mode API, a lookup cache, or an MCP server authored inside an integration folder is created account-global, and the write-back moves the file to its pool; the plan says so before anything is sent.

Namesakes. Immediately before each POST, the type's current listing (one per created type per push) is checked for an untracked namesake — the only way to see the same new file pushed from another clone of the tree. One holding exactly this document, or one in the same integration, refuses the create:

A pooled type's namesake with different content is a note, and the create proceeds — names are not unique account-wide. --allow-duplicate-names skips the check and its listings.

After a create, the file is the server's copy. The write-back renders the response the way pull does, _id included, at the resource's canonical path (scripts/New Hook.js → scripts/New Hook/script.js (_id … written back)), and the manifest records the server's hash.

Updates and the optimistic lock

A modified tracked document is a PUT of the whole file (minus the volatile fields the tree strips). The API has no conditional write, so the lock is best-effort by construction: one GET immediately before each PUT, comparing the lastModified token recorded at pull time. A mismatch means someone changed the server copy since your pull:

Three exits, all per file: celigo diff <path> shows the two versions, celigo pull --force <path> takes the server copy of that one resource, celigo push --force-remote <path> overwrites it. The lock is lenient where nothing stands to be lost, and strict everywhere else:

  • A server copy that already holds exactly the document you are about to send — an interrupted push whose PUT landed, a lost response — is adopted as up to date; nothing is sent.

  • A token that moved while the server copy still renders to exactly what was pulled is not a change: connections enable-debug, a flow run, or the UI's debug toggle bump lastModified and touch only volatile fields, so the write goes through.

  • Where a token is missing on either side — a sync document has no lastModified at all — the rendering is the lock: the write goes through when the server copy still renders to what was pulled and is refused as remote changed since pull otherwise. The lock never falls open; a missing token beside a dirty data companion (cache data, a sync's datasets) refuses as cannot verify the server copy.

After a push, the file is the server's copy. Updates and creates alike write the server's response back through the same rendering pull uses, and the manifest records that hash — so status is clean because the tree is server-identical, the pull right after a push is a no-op, and a secret you typed is masked again immediately. Anything the server silently dropped is said on stderr:

_registeredConnectionIds and _registeredLookupCacheIds are the classic case — a PUT ignores them; registration goes through celigo integrations register-connections and register-lookup-caches. A pushed name change moves the resource's folder, links included.

A connection update carries the server's debugDate. A connection's debugDate is the one debug timer a full PUT erases when omitted, so push copies the server's current value into every connection update; the file never holds it and diff stays clean. Updating an abstract flow prints a note that its instances inherit the change silently.

A lost update is made visible. After each document PUT, one GET re-reads the server copy. A document that no longer matches what was just written means another writer landed behind the PUT:

The manifest then records the server's hash, so status shows the file modified instead of a clean tree that differs from the account. --no-verify skips that GET.

Prune deletes

Without --prune, nothing is ever deleted and no confirmation prompt appears. With it, every tracked file you removed is planned as a delete, held to the same lock as an update: one GET per planned delete at plan time, so --dry-run --prune reaches the network and needs a token that can read the account. A server copy modified since the pull is refused:

The push asks [y/N] before deleting; -y skips the prompt, and declining skips the deletes while the rest of the plan is sent. The lock is re-checked right before each DELETE; --force-remote skips both checks. Deletes run tier by tier with dependents first (instance flows before their parent), a DELETE that finds the resource already gone is reported already gone, and a tracked file that is back on disk by the time its DELETE runs — a celigo pull during the prompt restores missing files — fails the item rather than deleting a resource the tree holds again.

⚠️ --prune deletes resources from the account. Run celigo push --dry-run --prune first and read every delete line. In a scoped tree, deleting a flow leaves its steps unreferenced; the push names them (note: no longer referenced by any tracked resource in this tree: …) so you can delete their files and push --prune again.

Results and exit codes

One line per item, in plan order whatever order the requests completed in. The summary counts created, recovered, updated, up to date, deleted, already gone, failed, and skipped; in --format json, results[].status is one of created, recovered, updated, up-to-date, deleted, deleted-gone, failed, skipped, and every plan note and prompt is on stderr so stdout is only the report. Per-file failures never abort the rest.

Exit code
When

0

Every planned item landed, or the plan held only withholdings.

1

Any item failed or was refused — --dry-run included — or the manifest could not be saved.

130

Interrupted with Ctrl-C.

Interrupting a push

Ctrl-C once finishes the requests already on the wire (up to --concurrency of them), saves what landed, reports every remaining item as skipped (N items not attempted (interrupted) — push again to continue), and exits 130. Press again to abort at once. Either way, a re-run converges on the same server state as an uninterrupted push:

  • Updates and deletes are idempotent. A PUT that landed before its token was recorded is adopted as up to date; a DELETE that finds the resource gone is already gone.

  • Creates record their intent first. Before the POST, the manifest records the create's type, path, name, and time. On the next push, an untracked namesake the server stamped no earlier than that intent, holding exactly the document the create body carried, is adopted as recovered instead of created twice. A namesake whose content differs is refused by name and id — inspect it, delete it, remove the local file, or rename the local resource (its name field) and push again. Because the intent lives in the committed manifest, a teammate resuming from your commit reconciles the same way.

Scoped trees

A tree pulled with --integration may write its own integration and everything the integration reaches, and never another integration's. A create or edited update whose document names another integration — in an explicit _integrationId, one inferred from the folder it sits in, or a new integration.json of its own — is refused at plan time, and its same-push children are blocked:

--allow-dependencies has one meaning — write into an integration this tree does not own — and lifts both that refusal and the withholding of borrowed dependencies. A create the integration will not reach (a stack nothing references, a flow with no integration) is legitimate authoring: push notes it and goes ahead. celigo lint reports the same refusals offline as scope errors.

The account binding

Push settles the tree's identity before its first read of the account: the manifest's API host offline, then the token owner with one GET /v1/tokenInfo. A tree bound to another host or another account user is refused, however the profile was selected. push --dry-run runs the same check whenever a token is configured and the plan has items; without a token it plans offline and says so (note: no API token configured — offline dry run: …). A different profile name on the same account is only a stderr note, so an env-only CI job can push. --allow-account-mismatch is the only override. The full verdict table, including a tree pulled while the identity probe was failing, is in Teams, git, and CI.

Concurrency and the tree lock

Requests run through a bounded pool — --concurrency <n> (1–16), else CELIGO_CONCURRENCY, else 8 — with the order kept where it matters: creates in dependency rounds, deletes dependents-first, and the report in plan order. A 429 Too Many Requests is retried after the server's Retry-After. pull and push hold .celigo/local/lock for the run; a second run in the same directory fails fast, a lock whose process is gone or older than six hours is taken over with a note, and --force-lock is for a run that died on another machine sharing the directory. push --dry-run never locks.

Two pushes from two machines are not serialized by the lock — one writer per tree at a time is a team rule, and the deploy job's concurrency group in Teams, git, and CI is how CI enforces it.

Gotchas

  • push --dry-run is the review artifact. Post it on the pull request together with celigo diff --exit-code; every guard runs at plan time, so what the dry run accepts is what the push sends.

  • Commit the manifest after every push. A successful push refreshes the lock tokens in .celigo/manifest.json; a teammate pushing from a checkout without that commit is refused as remote changed since pull.

  • Do not pass --force-remote in CI. Re-pull and re-commit the tree instead; the flag overwrites whatever someone changed in the UI.

  • --no-create in the deploy job keeps a stray draft from becoming a resource; creates then go through a reviewed pull request that pushes from a developer machine, or through a job that allows them deliberately.

  • lastModified never appears in a file. The lock token lives in the manifest; a diff of two tree files never shows it.

Last updated

Was this helpful?