> 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/lint-and-resolve.md).

# Lint and resolve

Two offline commands for a [local tree](/cli/local-tree.md). `celigo lint` validates the documents against the platform's published API schemas, and checks with six logic rules that each one holds together, before you push; `celigo resolve` turns any reference — a link, a path, a `type:id`, an id — into the document's path. Neither needs a token or a profile.

## `celigo lint`

Validate the tree's documents against the platform's published API schemas — the `@celigo/api-specs` package on npm, the same schemas behind the [OpenAPI specs](https://developer.celigo.com/api/using-the-api/openapi-specs). The push guards that can be decided offline run too, with the same wording `push --dry-run` uses, so a clean lint is most of a clean plan. Since celigo-cli 2026.10.1, lint also runs six [logic rules](#logic-rules) that ask whether a document holds together — the rules `push` and `promote` run at plan time.

**Signature**

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

**Arguments**

| Argument    | Type                             | Required | Description                                                                                                                                                                                        |
| ----------- | -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[refs...]` | file path, `type:id`, or bare id | No       | Narrow the check to these resources. Without refs, every tracked and new document is checked. A script's `.js` source selects the script and the findings in every document whose handler runs it. |

**Flags**

| Flag            | Type    | Default | Description                                                                                    |
| --------------- | ------- | ------- | ---------------------------------------------------------------------------------------------- |
| `--dir <path>`  | string  | `.`     | Tree root directory.                                                                           |
| `--strict`      | boolean | `false` | Treat every warning as an error.                                                               |
| `--specs <dir>` | string  | —       | Pin a local schema pack directory (a `schemas.json`) instead of the cached or bundled schemas. |

**Output**

One line per finding — severity, file, JSON pointer, message — then a summary and, on stderr, the schema pack that was used:

```
warning  connections/Shopify/connection.json  /http/unencrypted/region  'region' is not a documented field here
         already on this account: 3 clean connection documents carry /http/unencrypted/region
error    exports/Get Orders/export.json  /hooks/preSavePage/function  function 'preSave' is not defined in scripts/Hooks/script.js (it defines preSavePage) — fix the name here or add the function to the script
error    exports/New Orders/export.json  /http/relativeURI  expected string, found number  (push will create this)
2 errors, 1 warning across 212 documents.
specs: integrator-api-specs 935a10f1 (2026-10-13, bundled 0.2.105)
```

A clean tree prints `Clean — N documents checked.` The exit code is `1` when there is at least one error, and `0` otherwise.

### What lint reports

| Finding                                                                                                        | Severity         | Meaning                                                                                                                                                                                                                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Unknown field, type mismatch, enum miss                                                                        | warning or error | A shape the schema does not describe. The account outranks the specs (below).                                                                                                                                                                                                                                                                                        |
| Masked credential placeholder on a create                                                                      | error            | A `"******"` mask (four or more asterisks and nothing else) in a new document would be sent as the credential.                                                                                                                                                                                                                                                       |
| The `responseMapping` object wrapper                                                                           | error, always    | `fields` or `lists` as an object at any step position, in flows, tools, and APIs. The platform accepts it and erases the step's mappings, so no evidence downgrades it and the schema pack is not consulted. A value that is neither object nor array is `is not an array (found <type>)`.                                                                           |
| `invalid-json`                                                                                                 | error            | A tracked file that is not a JSON object: `unresolved git merge conflict (conflict markers at line N)`, `invalid JSON: <position>`, or `not a JSON object`. A pull-generated `.link.json` or `.external.json` that no longer parses is reported the same way.                                                                                                        |
| `conflict-markers`                                                                                             | error            | A `script.js` or `instructions.md` holding git conflict markers.                                                                                                                                                                                                                                                                                                     |
| `unresolved-reference`                                                                                         | error            | A path placeholder or `@bindings/` key that names neither a tracked file, nor a new file in the tree, nor a bound id — one error per reference, at its pointer.                                                                                                                                                                                                      |
| `location`                                                                                                     | error            | A new file whose folder push cannot honor: an untracked integration folder, an unknown flow or API group, an `instances/` folder with no parent.                                                                                                                                                                                                                     |
| `scope`                                                                                                        | error            | In a tree pulled as one integration, a new file or edited document whose owner is another integration. Unedited borrowed dependencies are not reported.                                                                                                                                                                                                              |
| `unfinished`                                                                                                   | error            | A resource an interrupted pull or push left half-done: one finding on its document that names the command that finishes it, and none of its files is linted until then — in a swap of names, its recorded path holds the other resource's document. Since celigo-cli 2026.10.1. See [Interrupted pulls and pushes](/cli/local-tree.md#interrupted-pulls-and-pushes). |
| `hook-function`, `script-syntax`, `lookup-reference`, `router-reference`, `handlebars-syntax`, `topic-binding` | error or warning | The logic rules: whether the document holds together. See [Logic rules](#logic-rules). Since celigo-cli 2026.10.1.                                                                                                                                                                                                                                                   |

**The account outranks the specs.** The published schemas are stricter than the API on real data, so a finding is downgraded to a warning when any server-identical document of the same type on this account already exhibits the shape — a field existing rebuts "unknown", the same JSON type rebuts a type mismatch, the exact value rebuts an enum miss. A create that introduces a shape nothing on the account backs is an error, because there is no evidence the platform accepts it. Spec-contradicted findings are appended to the per-machine `.celigo/local/lint-feedback.jsonl` — field paths and type classes only, never values — as feedback for the specs. Logic findings never reach it: they are not claims the schemas make.

**`--strict` is not yet a usable CI gate.** A pristine pull of a real account produces warnings, so `--strict` fails everything until the published schemas catch up. Plain `celigo lint` (errors only) is the gate to run.

### Logic rules

> Added in celigo-cli 2026.10.1.

The schema walk asks whether a document is shaped right; six logic rules ask whether it holds together. They are offline and deterministic, run whatever the schema pack says, and report each finding at the referencing slot — the field that names the missing function, lookup, router, or topic — so the [Flow Canvas](/cli/vscode-extension.md) can pin it to its step.

| Rule                | Reports                                                                                                                                                                                                                                                                                                                               | Reported at                                                    |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `hook-function`     | A handler slot — a hook such as `hooks.preSavePage`, a `filter.script`, a router's `script` while the router routes by script, an API's handler — names a `function` its script never mentions. A script that exports its hooks by assignment (`exports.x = …`, `module.exports = {…}`) is told that such exports are not read.       | The slot's `function`                                          |
| `script-syntax`     | A script's `.js` source does not compile. It is compiled, never run; the platform's `import … from 'integrator-api'` module form is understood, the line and column are your source's, and top-level `await` and `import.meta` are left unjudged.                                                                                     | The `.js` file, and the `_scriptId` of every slot that runs it |
| `lookup-reference`  | A mapping's `lookupName` (Mapper 2.0 at any depth, Mapper 1.0 `fields` and `lists`, a `conditional`, an HTTP import's `existingLookupName` or `ignoreLookupName`) names no lookup the import defines — at the top level, under its adaptor (`http.lookups`, `netsuite_da.lookups`, …), or in a nested `lookups[]` of its own subtree. | The field that names the lookup                                |
| `router-reference`  | A branch's `nextRouterId` names no router of the flow, API, or tool (the platform answers `422`); in an API or tool, a branch that neither chains to another router nor ends in a response or output step (a flow branch may end anywhere); a router no branch reaches from the entry router, which is always a warning.              | The `nextRouterId`, the branch, or the router                  |
| `handlebars-syntax` | A template does not compile, in a field the published spec tags as Handlebars — URIs, bodies, header values, SQL queries, file names and paths, auth templates, a tool's `input.traceKeyTemplate`, a response mapping's `extract` — or in a mapping `extract` that holds an expression.                                               | The template field                                             |
| `topic-binding`     | An Event Streams listener (an export with `adaptorType: TopicExport`) without `type: "stream"`, which the platform saves without complaint; a listener's or publisher's `topic._topicId` naming a topic the tree does not hold.                                                                                                       | `/type`, or `/topic/_topicId`                                  |

`handlebars-syntax` judges delimiters, blocks, and literals only — an unclosed `{{#each}}`, a `{{/if}}` with nothing open, `{{else}}` outside a block, a `{{{` closed with `}}`, a stray brace, an unterminated string. It never judges helper names, and never faults the platform's own path syntax (`PO1[*].[Unit Price]`). One common case: in a JSON body, `{"id": {{{record.id}}}}` glues the body's closing brace to the template's closer and does not compile; put a space before the last brace.

**Severity follows who introduced the finding.** A logic finding is an error when it sits in, or read, a file that is new or locally modified — a hook's script source counts, so a script edit that drops a function an unchanged export's hook names is an error at the export's slot. It is a warning when every file involved is unchanged since the pull: the shape already exists on the account, so a pristine pull adds no errors and plain `celigo lint` stays a usable gate. `--strict` promotes these warnings like every other.

**A rule that cannot decide stays silent.** `hook-function` leaves alone a slot that also names a stack, names no function, or names a script the tree does not hold. `topic-binding` judges an unknown topic id only in a tree pulled for the whole account that tracks topics: a tree pulled as one integration holds only the topics that integration reaches, and a `.celigoignore` pattern that could keep a topic out of the pull — one anchored in `topics/`, a wildcard reaching into it, or a bare name, which matches at any depth — silences the check, as does a path placeholder.

**Lint one script and what it takes down.** `celigo lint scripts/<Name>/script.js` lints that source, tracked or new, and shows the findings in every document whose handler runs it.

**Push and promote run the same rules at plan time.** There, an error refuses the item unless you pass `--allow-lint-errors`; see [The logic lint gate](/cli/local-tree/push.md#the-logic-lint-gate). Topic documents have no schema in the bundled pack, so the schema walk skips them — they are not counted among the documents checked — and only the logic rules and push's guards read them.

### Where the schemas come from

Each release bundles the schema pack version it was built with. Since celigo-cli 2026.10.1 that is `@celigo/api-specs` 0.2.105: 739 schema definitions over the same 20 resource types (2026.9.6 bundled 0.2.14, with 704). At most once every 24 hours — never in CI, and never failing or changing a command's outcome — the CLI checks npm for a newer `@celigo/api-specs` pack, verifies the tarball's integrity, and caches it under `~/.celigo/specs/<version>/` (the two newest versions are kept). Lint uses the newest valid pack it has, cached or bundled, and the provenance line names it: `auto-updated <version>`, `bundled <version>`, or `pinned` with `--specs`. A corrupt or missing cache degrades freshness, never correctness. With `--specs`, a directory without a `schemas.json` is refused, and since celigo-cli 2026.10.1 a `schemas.json` that cannot be read — no read permission, a directory by that name — is named in the refusal: `<dir>/schemas.json cannot be read as a specs pack (<error>).`

The check talks to `registry.npmjs.org` directly and does not consult an `.npmrc` mirror; on a mirror-only network the bundled pack keeps working. Opt out with `CELIGO_NO_SPEC_UPDATE=1` for one environment, or `celigo config set specs_auto_update false` machine-wide. See [Configuration reference](/cli/getting-started/configuration.md).

**Example**

```bash
celigo lint
celigo lint "integrations/Shop/flows/Order Sync/flow.json"

# One script, and every hook it would break
celigo lint scripts/Hooks/script.js

# Pin a schema pack you unpacked yourself
celigo lint --specs ./api-specs
```

## `celigo resolve`

Print the path of the real document behind any reference. Offline — it reads only the tree and its `.celigo/` state, such as `.celigo/manifest.json` and, in a neutral tree, `.celigo/bindings.json`.

**Signature**

```bash
celigo resolve <ref> [flags]
```

**Arguments**

| Argument | Type   | Required | Description                                                                                                                                                                                                                                                                                                          |
| -------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<ref>`  | string | Yes      | A link (a `.link.json` pointer or a symlink), any tree file path, `type:id`, a bare id, or — in a [neutral tree](/cli/local-tree/neutral-trees.md) — an `@bindings/<key>` key. Since celigo-cli 2026.10.1 a file path may be absolute inside the tree as well as relative to it; a path outside the tree is refused. |

**Flags**

| Flag           | Type    | Default | Description                                         |
| -------------- | ------- | ------- | --------------------------------------------------- |
| `--dir <path>` | string  | `.`     | Tree root directory.                                |
| `--open`       | boolean | `false` | Open the resolved file with the VS Code `code` CLI. |

A script resolves to its editable `script.js`; every other type resolves to its document file. `--format json` adds the type, the id, and the absolute target. In a neutral tree, `celigo resolve @bindings/connection/shopify` prints the id and name the key resolves to in **this** environment: `6a1b2c3d4e5f6a7b8c9d0e1f (Shopify)`.

Since celigo-cli 2026.10.1, `resolve` follows local moves: a resource whose folder you moved or renamed resolves to where its document now sits — a parked one to its document in the ignored folder — and the companion files and links inside a moved folder resolve with it. A resource an interrupted pull or push left half-done has no one document yet, so it is refused, naming both paths and what finishes it:

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

A path outside the tree is refused by name: `'<ref>' is outside the tree at <root> — name a file inside it, a path relative to it, type:id, or a bare id`.

**Example**

```bash
# Follow a link to its document
celigo resolve "integrations/Shop/flows/Order Sync/exports/Get Orders.json"
# exports/Get Orders/export.json

# By id, by type:id, and opened in VS Code
celigo resolve 6a1b2c3d4e5f6a7b8c9d0e1f
celigo resolve export:6a1b2c3d4e5f6a7b8c9d0e1f --open

# A neutral tree's binding, in this environment
celigo resolve @bindings/connection/shopify
```

## Related

* [Push changes](/cli/local-tree/push.md) — the same guards and logic rules at plan time, plus the ones that need the account.
* [Neutral trees](/cli/local-tree/neutral-trees.md) — `@bindings/` keys and `.celigo/bindings.json`.
* [Configuration reference](/cli/getting-started/configuration.md) — `specs_auto_update` and `CELIGO_NO_SPEC_UPDATE`.
