> For the complete documentation index, see [llms.txt](https://developer.celigo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.celigo.com/cli/local-tree/status-and-diff.md).

# Status and diff

Two views of a [local tree](/cli/local-tree.md): `celigo status` is offline and answers "what did I change since the last pull"; `celigo diff` fetches the live account and answers "how do my files differ from the server". Both are informational by default and become CI gates with `--exit-code`, like `git diff --exit-code`.

## `celigo status`

Compare the tree against `.celigo/manifest.json`. No API call, no token, no profile check — it is instant and safe anywhere.

**Signature**

```bash
celigo status [flags]
```

**Flags**

| Flag           | Type    | Default | Description                                                                                                                                                                                                            |
| -------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dir <path>` | string  | `.`     | Tree root directory.                                                                                                                                                                                                   |
| `--exit-code`  | boolean | `false` | Exit `1` when the tree is not clean — anything modified, missing, untracked, moved, parked, unfinished, or unreadable. Without it, `status` exits `0` even when the tree is dirty — a pull-request branch normally is. |

**Output**

Each list is in alphabetical path order:

```
modified:  exports/Get Orders/export.json
missing:   imports/Old Import/import.json
parked:    exports/Temp Orders/export.json → integrations/Sandbox/exports/Temp Orders/export.json  (in an ignored folder - not pushed, not deleted)
unfinished: imports/Post Orders/import.json → imports/Post Orders v2/import.json (an interrupted `celigo pull` was renaming it — run `celigo pull` to finish)
moved:     exports/Orders/export.json → exports/Get Orders/export.json  (folder names follow the resource's own name — `celigo pull` moves it back)
untracked: exports/New Orders/export.json  (no _id — push will create)
unreadable: integrations/Shop/connections/Shopify.link.json  unresolved git merge conflict (conflict markers at line 3)
```

| State        | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `modified`   | A tracked file's content differs from the hash recorded at the last pull or push.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `missing`    | A tracked file is gone. `push --prune` deletes the resource; `celigo pull` restores the file.                                                                                                                                                                                                                                                                                                                                                                                              |
| `parked`     | A tracked resource moved into a folder `.celigoignore` keeps out of reach. `push` never writes it, `push --prune` never deletes the live resource, and `celigo pull` leaves it where it is; move it back out of the ignored folder to make it an ordinary move again. Since celigo-cli 2026.10.1; earlier releases read it as `missing`, and `push --prune` deleted the live resource.                                                                                                     |
| `unfinished` | A resource an interrupted `celigo pull` or `celigo push` left half-done — renaming it, untracking it, adding it to the tree, or writing back a create. The line says what finishes it: `celigo pull`, or `celigo push` for an interrupted create, which adopts the resource instead of creating it again. Until then push neither updates nor deletes it under `--prune`. See [Interrupted pulls and pushes](/cli/local-tree.md#interrupted-pulls-and-pushes). Since celigo-cli 2026.10.1. |
| `moved`      | A resource folder was renamed locally. One event, never a `missing` plus an `untracked`, so `push --prune` cannot delete a resource over a rename. In a [neutral tree](/cli/local-tree/neutral-trees.md), whose documents carry no `_id`, an unedited move is recognized by content (since celigo-cli 2026.10.1); a folder moved onto the path another tracked resource was pulled at names that resource (`moved[].displaces` in `--format json`), whose own files are then `missing`.    |
| `untracked`  | A file the manifest does not know. With no `_id`, `push` creates it.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `unreadable` | A pull-generated `.link.json` pointer or `.external.json` stub that no longer parses (git conflict markers, invalid JSON). Nothing is pushed from such a file and `celigo pull` rewrites it, but the tree is not clean.                                                                                                                                                                                                                                                                    |

A clean tree prints `Clean — no local changes since <time of this machine's last pull>.` With `--format json` the report is a JSON object with the same lists.

A CRLF checkout is not an edit: a `.json`, `.js`, or `.md` file that differs from its recorded hash only by `\r\n` line endings or a leading UTF-8 BOM is clean. Since celigo-cli 2026.10.1 neither is a formatter: a `.json` document re-indented, its keys reordered, or its trailing newline dropped is compared as the document push would send, so only a changed value, key, or array order is an edit. A script or prompt whose server text mixes `\r\n` and `\n` stays clean after a checkout makes its line endings uniform (a tree an earlier release pulled gains what this needs on its next `pull`). Storage content files are compared byte for byte, because `\r\n` in them is data.

Since celigo-cli 2026.10.1 a `.celigoignore` that exists but cannot be read — no read permission, a directory by that name — stops `status`, `diff`, and the other tree verbs before they read anything, naming the file and the error. A tree without the file has no rules. See [`.celigoignore`](/cli/local-tree.md#celigoignore).

**Example**

```bash
celigo status

# A CI gate: fail when the checkout has changes its manifest does not know about
celigo status --exit-code
```

## `celigo diff`

Compare the tree against the **live** account. The CLI fetches fresh copies, ignores volatile fields, and prints field-level differences with JSON-pointer paths.

**Required:** read mode is enough.

**Signature**

```bash
celigo diff [refs...] [flags]
```

**Arguments**

| Argument    | Type                             | Required | Description                                                                                   |
| ----------- | -------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `[refs...]` | file path, `type:id`, or bare id | No       | The resources to compare. With no refs, `diff` compares only the files the checkout modified. |

**Flags**

| Flag                | Type    | Default | Description                                                                                                                                                                                                |
| ------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dir <path>`      | string  | `.`     | Tree root directory.                                                                                                                                                                                       |
| `--exit-code`       | boolean | `false` | Exit `1` when any compared resource is `changed`, deleted on the server, or local only, or one is `unfinished`. A `parked` resource is listed but never compared, so it does not count. Default: exit `0`. |
| `--concurrency <n>` | integer | `8`     | Requests in flight at once (1–16). `CELIGO_CONCURRENCY` sets the default.                                                                                                                                  |
| `--target <name>`   | string  | —       | Compare against a [promote](/cli/local-tree/promote.md) target instead of the tree's own account. See [Diff against a promote target](#diff-against-a-promote-target).                                     |

**Output**

Changed resources come first, then those deleted on the server, unfinished, local only, and parked, each group in tree-path order. Each entry is a JSON pointer with the server's value on the left and yours on the right:

```
changed  integrations/Shop/flows/Order Sync/flow.json
  ~ /disabled  true → false
  + /pageProcessors/1/_importId  "6a1b2c3d4e5f6a7b8c9d0e1f"
  - /schedule  "? */15 * * * *"
deleted on server  exports/Retired Export/export.json
unfinished (not compared)  imports/Post Orders/import.json → imports/Post Orders v2/import.json (an interrupted `celigo pull` was renaming it — run `celigo pull` to finish)
local only (push will create)  exports/New Orders/export.json
parked (in an ignored folder — not compared, not pushed, not deleted)  exports/Temp Orders/export.json → integrations/Sandbox/exports/Temp Orders/export.json
```

* An edit to `script.js` shows up under the document's `/content`: the editable source and its document are two faces of one resource. Lookup cache data is reported per key under `/data/<key>`, a sync's datasets under `/datasets/<name>`, a tampered storage file under `/content`.
* A changed value longer than the line is shown as a window around the first differing character on both sides, tagged `(differs at line L, col C)` — or `(differs at char N)` for a one-line value — so two script sources that share a long prefix are reviewable. `--format json` carries the whole values.
* **Secrets never print.** A local value replacing the server's `******` mask renders as `(set)`, and so does a new secret-named field (`password`, `token`, `secret`, `apiKey`, …) on a connection, iClient, or MCP OAuth provider. Since celigo-cli 2026.10.1, on those three types a changed secret is masked whatever the server held — an empty string or `null` included — and on every type a field the platform encrypts shows as `(set)` whatever it holds: a stack's keys, an export's webhook secrets, an MCP server's client secret, an API's ShipWorks password, even a number or object typed into a password by mistake. The masking happens in the entries themselves, so `--format json` carries no value either, and a diff posted as a CI comment is safe. `diff --target` masks the same way.
* A tracked file that is not a JSON object, or a `script.js` or `instructions.md` holding git conflict markers, is reported as `changed` with the problem in place of the local value and `(not compared)` on the remote side, rather than aborting the diff.
* **A resource an interrupted pull or push left half-done** is listed as `unfinished (not compared)` with what finishes it, and never compared: in a swap of two names, its path holds the other resource's document. Its old or new path, its folder, its `type:id`, or its id names it as a ref, and the path a swap ran through names both resources. A resource parked in an ignored folder is listed as `parked` and not compared either. Since celigo-cli 2026.10.1.
* Since celigo-cli 2026.10.1 an untracked document that carries an `_id` — a copied folder — is `local only (untracked)`, with the reason push skips it, rather than `local only (push will create)`. And a resource is `deleted on server` only when the server answers `404`: a fetch that times out or is refused fails the diff instead.

**The no-ref form compares only what you changed.** It cannot see the server drifting on a file nobody edited locally, and says so on stderr: `note: only locally modified files were compared — pass refs … to check unmodified resources for server-side drift`. To check specific resources for drift, name them:

```bash
# Only the files this checkout modified
celigo diff

# Named resources, whether or not they changed locally
celigo diff "integrations/Shop/flows/Order Sync/flow.json" export:6a1b2c3d4e5f6a7b8c9d0e1f

# A CI gate: exit 1 when anything differs
celigo diff --exit-code
```

A moved folder is compared where the file now sits; both the new path and the vacated one are accepted as refs. In a neutral tree that holds for an unedited move, which is recognized by content (since celigo-cli 2026.10.1). `diff` refuses a profile bound to a different account than the tree, with no override, and it never binds an unbound tree — it writes nothing.

### Diff against a promote target

`celigo diff --target <name>` compares against a [promote](/cli/local-tree/promote.md) target instead of the tree's own account. For every source document mapped in `.celigo/targets/<name>.json` it renders what `celigo promote --to <name>` would send — references remapped, server fields stripped — against the target's live copy. Every mapped resource is compared, refs or not (refs narrow the set); an unmapped one is `local only (promote will create)`, a vanished target copy `deleted in target` (since celigo-cli 2026.10.1; earlier releases labelled it `local only`). A document promote would refuse or block is not compared, and a stderr `note:` names the refusal. Like promote, it honors `.celigoignore`, local folder moves, parked resources, and unfinished ones.

The comparison uses the **target's** profile — or `CELIGO_TARGET_API_TOKEN` and `CELIGO_TARGET_BASE_URL` on a machine with no such profile — never the selected one, in whatever mode that profile is in: it only reads, so a read-mode production profile on a reviewer's machine serves it, and `CELIGO_MODE=read` does not stop it, while `promote` itself needs full mode. Since celigo-cli 2026.10.1 it does not need the selected source profile to exist or to pass `profile_pin_policy = strict`, and it refuses `--profile`, `--token`, and `--base-url`, which name the source session:

```
diff --target does not take --profile: the source is this tree (bound to the account it was pulled from) and the target is --target <profile>. Drop --profile — or did you mean --target sandbox?
```

It announces only the target on stderr, before the first request: `celigo: diff --target tree of profile 'prod' (…) → profile 'sandbox' → api.integrator.io`.

```bash
celigo diff --target sandbox
celigo diff --target sandbox "integrations/Shop/flows/Order Sync/flow.json"
```

## Related

* [Pull a tree](/cli/local-tree/pull.md) — `pull --force <path>` takes the server copy of one resource.
* [Push changes](/cli/local-tree/push.md) — `push --dry-run` is the plan; `diff` is the content.
* [Teams, git, and CI](/cli/local-tree/workflow.md) — the PR check that posts `diff --exit-code` and `push --dry-run`.
