> 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/pull.md).

# Pull a tree

`celigo pull` downloads the account, or one integration with `--integration <id>`, as a [local tree](/cli/local-tree.md). Re-pulls are incremental: unchanged files are untouched, a server-side rename becomes a folder move, and a locally edited file is never overwritten without `--force`.

**Required:** read mode is enough. A pull writes nothing to the account.

**Signature**

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

**Arguments**

| Argument    | Type                             | Required | Description                                                                                                                                                                                                                                                                                         |
| ----------- | -------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[refs...]` | file path, `type:id`, or bare id | No       | Only meaningful with `--force`: reset just these resources to the server copy. Refs without `--force` are an error. Since celigo-cli 2026.10.1 a file path may be absolute inside the tree, and the path a locally moved resource now sits at names it; a path outside the tree is refused by name. |

**Flags**

| Flag                       | Type    | Default    | Description                                                                                                                                                                 |
| -------------------------- | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dir <path>`             | string  | `.`        | Tree root directory.                                                                                                                                                        |
| `--integration <id>`       | string  | —          | Scope the tree to one integration and everything it references. Recorded in the manifest: later pulls stay scoped without repeating the flag.                               |
| `--with-dependents`        | boolean | `false`    | Also pull what *references* the scoped integration — an MCP server publishing its tool, a flow chaining into it. One lookup per owned resource. Needs an integration scope. |
| `--types <csv>`            | string  | all        | Only these resource types, for example `flows,exports,imports`. In a scoped tree the reference walk still sees the whole graph; `--types` filters what is written.          |
| `--prune`                  | boolean | `false`    | Delete the local files of resources that no longer exist on the server. A file you edited is kept; see [What a re-pull does](#what-a-re-pull-does).                         |
| `--force`                  | boolean | `false`    | Overwrite locally modified files with the server copy — every file, or only the refs named on the command line.                                                             |
| `--symlinks`               | boolean | first pull | Write links as real symlinks named like their target.                                                                                                                       |
| `--no-symlinks`            | boolean | —          | Write links as `.link.json` pointer files. Recommended for trees shared through git.                                                                                        |
| `--storage-files`          | boolean | `false`    | Also download each Celigo Storage file's content beside its metadata (may be large or binary).                                                                              |
| `--neutral`                | boolean | `false`    | Pull an environment-neutral tree. First pull only; the mode is recorded and permanent. See [Neutral trees](/cli/local-tree/neutral-trees.md).                               |
| `--untrack-orphans`        | boolean | `false`    | Stop tracking resources a scoped tree's integration no longer references but which still exist on the server.                                                               |
| `--allow-account-mismatch` | boolean | `false`    | Pull into a tree bound to a different account (host or token owner) anyway.                                                                                                 |
| `--concurrency <n>`        | integer | `8`        | Requests in flight at once (1–16). `CELIGO_CONCURRENCY` sets the default.                                                                                                   |
| `--force-lock`             | boolean | `false`    | Take the tree lock even if another pull or push appears to hold it.                                                                                                         |

Since celigo-cli 2026.10.1, a blank `--dir`, `--integration`, or `--types` — what an unset shell variable produces — is refused before anything runs, instead of widening the pull to the whole account or aiming it at the working directory: `The --dir value is empty. Provide a non-empty value (this usually means a shell variable was unset), or leave --dir out.`

**Example**

```bash
# The whole account, as a git-friendly tree
celigo pull --dir ./acme --no-symlinks

# One integration and everything it references
celigo pull --integration 5f83a9b2c7d3e8f1a2b3c4d5 --dir ./acme --no-symlinks

# Re-pull: incremental, keeps local edits
celigo pull

# Take the server copy of one resource you edited, keep every other edit
celigo pull --force "exports/Get Orders/export.json"

# Remove the local files of resources deleted on the server
celigo pull --prune
```

## What a re-pull does

Every document is decided before any is written. A pull:

* leaves unchanged files untouched, so a no-op pull leaves a committed tree byte-identical, manifest included;
* moves the folder of a resource renamed on the server (two resources that swapped names move in one pull);
* reports resources deleted on the server — `deleted on server: <key> (pass --prune to remove locally)` — and removes their files only with `--prune`, naming each: `pruned: <key> (deleted on the server; local files removed)`;
* keeps locally edited files and names them: `kept local edits: <path>`. A dirty file whose content is structurally identical to the server copy is refreshed (its hash and lock token), not kept. `--force` takes the server copy — of every file, or of the refs you name;
* leaves a parked resource — a tracked folder you moved into a `.celigoignore`d folder — where you put it, with `left parked: <path> — in an ignored folder; not recreated at the manifest path` (since celigo-cli 2026.10.1; see [`.celigoignore`](/cli/local-tree.md#celigoignore));
* finishes what an interrupted pull or push left `unfinished` (see [Interrupting a pull](#interrupting-a-pull));
* strips volatile fields (`lastModified`, debug timers, sizes) so the files only change when the configuration did.

**`--prune` keeps your edits.** Since celigo-cli 2026.10.1, a resource the server deleted whose files hold local edits is kept and stays tracked under `--prune`, because a locally edited file is never removed without `--force`:

```
warning: export:5f83a9b2c7d3e8f1a2b3c4d5 was deleted on the server, but exports/Get Orders/export.json holds local edits — kept, not pruned. `celigo pull --prune --force exports/Get Orders/export.json` removes it; to create the resource again from your file, remove its _id (standard trees) and push.
```

`status` keeps showing the edit, and a push of it is refused naming the deletion. `celigo pull --prune --force <path>`, or a whole-tree `--prune --force`, removes the files.

A file already sitting at a planned path that the tree does not track — a draft authored for `push`, a leftover of an interrupted pull — is never overwritten by a same-named server document unless the two are identical in substance; otherwise the pull leaves it alone with a warning, and `--force` overrides.

## Pull one integration

`--integration <id>` scopes the tree to one integration, everything it **owns** (flows, tools, APIs, MCP servers, its storage root, its registered lookup caches), and everything its resources **reference**, followed transitively. Placement does not change with scope: a resource sits at the same path whether you pulled one integration or the whole account, which keeps git diffs and promote stable.

References cross integration boundaries, so the closure follows them out. Resources another integration owns that came along because the scope references them are named per owner:

```
pulled 3 resource(s) owned by another integration, because this scope references them:
Provider (integration 'Provider', flow 'Downstream Flow', tool 'Shared Tool').
They are read-only context — push withholds them unless you pass --allow-dependencies.
```

Those resources are recorded as dependencies in the manifest, and [`push`](/cli/local-tree/push.md#scoped-trees) refuses to write them without `--allow-dependencies`.

**The scope is remembered.** Every later pull stays narrowed to the integration without repeating the flag, and `--types` then filters what is written while the reference walk still sees the whole graph. A scope integration that no longer exists on the account is a hard error.

**Once a scoped tree tracks a resource, it keeps tracking it** — refreshed on every pull — until the server deletes it or you untrack it. A tracked resource the integration merely stopped referencing is an *orphan of the scope*, kept and reported once:

```
note: 2 tracked resource(s) are not referenced by integration 'Consumer' but still exist on the server — kept tracked: exports/Consumer Export/export.json, imports/Tool Step/import.json. To delete them from the server, delete their files and run `celigo push --prune`; to stop tracking them here, run `celigo pull --untrack-orphans`.
```

`pull --prune` removes only what the server deleted; `pull --untrack-orphans` removes the orphans' files and manifest entries. The two compose.

**Topics come with the integration.** Since celigo-cli 2026.10.1 a scoped pull brings the Event Streams topics the integration registers (`_registeredTopicIds`), the ones its pulled steps bind (`topic._topicId`), and the ones an Integration App installed for it (`_integrationId`). Another integration that registers the same topic is not pulled with it: a shared topic belongs to no one integration. Topics pool at `topics/` whatever the scope; see [Event Streams topics](/cli/local-tree.md#event-streams-topics).

**Scripts are fetched closure-first.** A script's own references live in its source, so a scoped pull hydrates only the scripts its closure reaches, reads the references in their source, extends the closure, and repeats until nothing new appears. A hook that reads another export by URL brings that export in; a script nothing in the integration uses is never requested.

**`--with-dependents`** adds the other direction — what references the scope — through `GET /v1/<type>/<id>/dependencies` on the integration and the resources it owns. It costs one request per owned resource, which is why it is opt-in; a lookup that fails is a warning, not a failed pull. Account-level resources — a connection, a lookup cache, a topic — are never asked, so `--with-dependents` never starts from a topic: their dependents span the whole account, not the scope.

## Links: symlinks or pointer files

Links are real symlinks by default. `--no-symlinks` writes `.link.json` pointer files instead, one per referenced resource; use it for trees shared through git across platforms, or on Windows without Developer Mode (where symlink creation fails, warns once, and falls back automatically).

The choice is sticky. The first pull records the style in the manifest, later flagless pulls honor it, and an explicit conflicting flag is an error. Pass the flag together with `--force` to converge the whole tree to the other style; the report's link counts show the change. Pointer form carries one record per referenced resource, so a referenced script gets a single pointer instead of the `.js` + `.json` pair — which is why the link counts differ between the two styles.

## Celigo Storage

`storage/` mirrors the service's hierarchy: a folder is a directory, a file is a `<name>.json` metadata stub. `--storage-files` downloads the contents instead, under the item's real name (`storage/docs/report.csv`), hash-tracked so `status` and `diff` notice tampering, and re-downloaded only when the item changed. A pull without the flag leaves existing content files exactly as they are.

Contents may be large or binary. For a git-tracked tree either skip the flag or ignore the content while keeping the stubs:

```
storage/**
!storage/**/
!storage/**/*.json
```

The middle line re-includes the folders, without which git never looks inside them for the stubs.

A root storage folder whose metadata carries an integration's `_integrationId` nests under `integrations/<Int>/storage/<Folder>/…`; loose root files and everything else stay in the account-wide `storage/`. Storage is pull-only: `push` refuses storage items, and the [`celigo storage`](/cli/commands/storage.md) commands are the write path. Accounts without the Storage entitlement report the type as skipped, not failed.

Since celigo-cli 2026.10.1 the walk of the storage folders runs on the pull's pool: breadth-first, with up to `--concurrency` folder listings in flight. A folder deleted on the server while the walk runs is left out with its contents, and the walk completes.

## Lookup cache data

A lookup cache's folder holds `lookup-cache.json` and `data.json`: the cache's entries in the shape `lookup-caches put-data` accepts, `{ "data": [{ "key": …, "value": … }] }`, sorted by key. Since celigo-cli 2026.10.1 a value keeps its JSON type through `pull`, `diff`, and `push` — `null`, a number, a boolean, a string, or an object. Earlier releases kept only string values and left the other entries out of the file.

A pull reads a cache's entries again only when the cache changed on the server since the last pull (every data write moves its `lastModified`) or with `--force`, so a `data.json` an earlier release wrote can still lack the non-string entries. `push` never deletes the entries such a file is missing, and writes them into the file after it syncs; `celigo pull --force "lookup-caches/<Name>/lookup-cache.json"` rewrites the file in full. The entries push refuses at plan time are listed in [What push refuses](/cli/local-tree/push.md#what-push-refuses).

## What a pull warns about

* **Dangling references.** A reference to a resource that does not exist on the account produces no link, and the pull says so. It stays quiet where absence is normal: types a `--types` narrowing did not fetch, iClient targets (a stock connector's OAuth app is Celigo's), and non-id template tokens.
* **Hardcoded ids.** A `settings` object or a script body has no schema, so the pull scans them for 24-hex strings that resolve to a resource on this account and reports them as `hardcodedRefs` — one summary line in text output, full detail (holder, pointer, target) under `--format json`. Promote rewrites those ids; a standard push does not.
* **Integration App documents**, once per app: `note: 154 resource(s) belong to Integration App 'Shopify - NetSuite' (…) — read-only in this tree; edits are withheld from push.`
* **A rename onto a kept file.** When the server renamed a resource onto the path of a file this pull kept (your local edits), the renamed resource stays at its old path and the warning names both. Resolve the kept file — push it, or `pull --force <path>` — and pull again.
* **Unentitled types** (Storage, B2B) are reported as skipped.

## Account binding

A pull into an existing tree checks the account first: the manifest's API host offline, then the token owner with one `GET /v1/tokenInfo`, before the first listing. A token for another account is refused before that account is read at all. `--allow-account-mismatch` is the override. Since celigo-cli 2026.10.1, a `tokenInfo` answer that names no account (no `ownerUser`) counts as a failed probe: the pull warns, continues on the host match, and binds nothing. See [Teams, git, and CI](/cli/local-tree/workflow.md#the-account-binding).

## Concurrency and the tree lock

Per-resource requests — script hydration, cache data, datasets, `--with-dependents` lookups, the storage folder walk — run through a bounded pool: `--concurrency <n>` (1–16), else `CELIGO_CONCURRENCY`, else 8. Storage content downloads stay at three at a time. A `429 Too Many Requests` is retried after the server's `Retry-After`.

`pull` and `push` hold `.celigo/local/lock` for the run, so a second `pull` or `push` in the same directory fails fast instead of recording the wrong baseline:

```
another celigo pull is running in this tree — celigo pull (pid 4242 on host, started 2026-09-10T14:02:11Z) holds .celigo/local/lock. Wait for it to finish; if it is not running any more, pass --force-lock (or delete the lock file).
```

A lock whose process is gone on this machine, or older than six hours, is taken over with a note. `--force-lock` is for a run that died on another machine sharing the directory. `status`, `diff`, `lint`, `resolve`, and `push --dry-run` never lock.

## Interrupting a pull

> Added in celigo-cli 2026.10.1.

Ctrl-C, `SIGTERM`, and a closed terminal (`SIGHUP`) are held until a pull's writes and the manifest save are done. A kill nothing can hold — `SIGKILL`, the out-of-memory killer, a power loss — is what `.celigo/local/pull-journal.json` is for. Before a pull removes the old files of a resource the server renamed, lets go of an orphan under `--untrack-orphans`, or writes the files of a resource new to the tree, it records them there, and the record is cleared after the manifest save. Killed in between, the resource is `unfinished` — never a deletion `push --prune` would send, nor a new file push would create:

```
unfinished: exports/Get Orders/export.json → exports/Orders/export.json (an interrupted `celigo pull` was renaming it — run `celigo pull` to finish)
```

Run `celigo pull` to finish it. An orphan `--untrack-orphans` was letting go is the exception: run `celigo pull --untrack-orphans` again to finish, or a plain `celigo pull` to keep tracking it. The next pull takes a file holding exactly what the journal recorded as its own, not as a local edit or a draft; a file you changed since is kept, as any local edit is. `celigo pull --force <path>` also takes the path `status` prints for an unfinished resource. A pull whose write phase fails — a full disk — leaves the renamed resource it could not finish `unfinished` the same way, and the next pull writes it at its new path, or removes the file and reports it `pruned` when the server has deleted the resource since. [Interrupted pulls and pushes](/cli/local-tree.md#interrupted-pulls-and-pushes) lists what the other verbs do meanwhile.

A first pull writes `.celigo/.gitignore` before its first request, so the lock file a first pull killed mid-fetch leaves behind is already ignored by git and cannot be committed by accident.

## Gotchas

* **Neutral pulls fetch the whole account** even when scoped — the symbol table needs every path and binding name. Budget tens of seconds per pull on a large account.
* **Large accounts.** An unscoped `--no-symlinks` tree of 13,000 resources is about 46,000 files, 20,000 of them link pointers. Expect about 90 s for the first pull and 40 s for re-pulls, run `git repack -a -d` after the first commit, and use a scoped tree when you can.
* **Refs need `--force`.** `celigo pull <path>` alone is an error; the refs grammar exists to reset named resources.
* **A tree that holds a topic needs celigo-cli 2026.10.1 everywhere.** The first pull that writes a topic moves the manifest to layout 6, which CLI 2026.9.6 refuses as pulled with a newer CLI, and the tree never goes back to layout 5. Upgrade every machine and CI job that works on the tree first. See [The `.celigo/` directory](/cli/local-tree.md#the-celigo-directory).
* **Symlinks are the one kind of link a tree holds.** A symlinked content directory or document is refused on every manifest load (`<path> is a symbolic link — the tree must not contain symlinks …`), because git tracks symlinks and a committed one could point outside the tree.

## Related

* [Status and diff](/cli/local-tree/status-and-diff.md) — what changed locally, and against the account.
* [Push changes](/cli/local-tree/push.md) — write the edits back.
* [Neutral trees](/cli/local-tree/neutral-trees.md) — `pull --neutral`.
* [Teams, git, and CI](/cli/local-tree/workflow.md) — the repository layout and what to commit.
