> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# blobhub workflow diff

> Report every local/remote difference — presence, topology, code, remote edits — writing nothing

`diff` compiles your package in memory, fetches the definitions the revision actually holds, and reports the gap
between them. It writes no file, uploads nothing, and creates nothing.

It is the command to run before a [`deploy`](/cli/workflow/commands/deploy), and the command to run after one
stops. Almost every hint the CLI prints points here, because `diff` needs only read access and refuses to fail
on the input you are trying to understand.

## Synopsis

```bash theme={null}
blobhub workflow diff -f <manifest> [--definition <alias>]
```

| Flag           | Default                          | Description                      |
| -------------- | -------------------------------- | -------------------------------- |
| `--file`, `-f` | required                         | Path to the workflow manifest.   |
| `--definition` | every definition in the manifest | Limit to this alias. Repeatable. |

There is no `--dry-run` and no `--force`: `diff` has no side effect to withhold and nothing to overwrite.

## It always exits `0`

`diff` is a report, not a gate. Every state it can discover — a definition missing on either side, a topology
that has moved, code that no longer matches, a port edited in the visual editor, even a package that does not
compile — is **printed**, and the exit code stays `0`.

That is deliberate. `diff` is the escape hatch that `MANIFEST_INVALID`, `MANIFEST_NOT_FOUND`,
`REVISION_NOT_WRITABLE` and `REVISION_NOT_ACCESSIBLE` all send you to, so dying on a broken import would kill
the CLI's own diagnostic on precisely the input a user runs it to understand.

What can still fail is what `diff` cannot reach: an unreadable manifest, a blob or revision it cannot resolve, a
credential problem, a transport failure. Those exit `1` like anywhere else. Once `diff` has a manifest, a
revision and a listing, nothing it finds changes its exit code — so **do not use it as a CI gate**. To fail a
build on a real difference, read the `--json` payload, or use `build --strict-imports` for import certainty and
`check` for validity.

## What it reports

One block per definition, drift first:

```text theme={null}
drift: 1 definition(s) on the revision are not in the manifest and were left untouched: playground/checkout_lab (removing them requires 'blobhub workflow prune')
workflow/checkout_flow: both
  components added: c7
  connections changed
  code changed
  state: REMOTE_EDIT
workflow/legacy_flow: remote_only
```

| Line                                      | Meaning                                                                                                                                                  |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `presence` — `both`                       | The definition exists locally and on the revision.                                                                                                       |
| `presence` — `local_only`                 | A `source` file exists; the revision has no such `(category, alias)`. A `deploy` would create it.                                                        |
| `presence` — `remote_only`                | The revision has it; the local `source` file does not exist. A `deploy` would fail asking you to `pull`.                                                 |
| `presence` — `absent`                     | Neither side has it: the manifest names it, but there is no file and no definition.                                                                      |
| `components added` / `components removed` | Component **ids** present on one side only. A `local_only` definition reports all of its components as added, since there is nothing to compare against. |
| `connections changed`                     | The two `connections` lists are not identical. This is an exact comparison, so a reordering counts as a change.                                          |
| `code changed`                            | For at least one bound component, the freshly compiled content hashes differently from what the revision holds.                                          |
| `compile error: <CODE>: <message>`        | Compilation failed. `code changed` stays absent for that definition — once the compile failed, whether the code changed is unknowable.                   |
| `state`                                   | The remote-edit state of the **remote** definition's bound code ports.                                                                                   |

Under `--json` the payload carries `definitions` — each with `category`, `alias`, `presence`,
`components_added`, `components_removed`, `connections_changed`, `code_changed`, `compile_error` and `state` —
plus `drift`, alongside the envelope's `schema_version`.

## The `state` line is about the platform's copy

`build` and `deploy` classify the code port in your local definition file. `diff` classifies the **remote** one,
which is what makes it the only command that sees a visual-editor edit before you have pulled it.

| `state`         | What it means                                                          | What a `deploy` would do                                         |
| --------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `MANAGED_CLEAN` | The revision's code is what the CLI last built.                        | Overwrite it.                                                    |
| `REMOTE_EDIT`   | The revision's code was edited after the last build.                   | **Overwrite it** — the gate reads your local file, not this one. |
| `UNMANAGED`     | The revision's code was never built by this CLI, or the port is empty. | Overwrite it.                                                    |

So `REMOTE_EDIT` here is a warning to act on, not a refusal you will hit later. Pull it, or eject it, before you
deploy over it. When a definition binds several code components, the worst state wins, because that is the one
that decides what happens next.

`state` is absent when the definition is not on the revision, and when the manifest binds no code components to
it.

## Compilation is offline here

Like `build`, `diff` reads the import allowlist from the local cache rather than fetching it, so it does not need
an `admin` key and works with a stale or missing cache. Unresolved imports raise `IMPORT_UNVERIFIED` as an
advisory; `--strict-imports` does not exist on this command, since a report has nothing to be strict about.

## Errors

| Code                                                                                                                                                                                 | When                                                                                                                                                                                             |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MANIFEST_NOT_FOUND`, `MANIFEST_INVALID`, `MANIFEST_TYPE_MISMATCH`, `MANIFEST_VERSION_UNSUPPORTED`                                                                                   | The manifest is absent or unusable. A `source` file that does not exist is *not* one of these here — `diff` reports it as `remote_only` or `absent`.                                             |
| `DEFINITION_ALIAS_INVALID`, `DEFINITION_CATEGORY_UNKNOWN`, `DEFINITION_NOT_ACCESSIBLE`                                                                                               | An alias fails the pattern; a category cannot be inferred or contradicts the document; a `--definition` alias is not in the manifest.                                                            |
| `BLOB_NOT_ACCESSIBLE`, `BLOB_NOT_WORKFLOW`                                                                                                                                           | The blob is absent or unreachable with this key, or is not a workflow blob.                                                                                                                      |
| `REVISION_NOT_ACCESSIBLE`, `REVISION_NOT_WRITABLE`                                                                                                                                   | The revision is unreachable, or its status is not `ready` — or its phase is none of `draft`, `commit`, `managed`. Reading is the laxer gate, which is why `diff` answers where `deploy` refuses. |
| `FORMAT_UNSUPPORTED`, `FORMAT_PARSE_ERROR`                                                                                                                                           | A `source` has an unsupported extension, or does not parse.                                                                                                                                      |
| `CREDENTIALS_NOT_FOUND`, `PROFILE_NOT_FOUND`, `INSECURE_CREDENTIALS_PERMISSIONS`                                                                                                     | No usable credential, or a credentials file the CLI refuses to read.                                                                                                                             |
| `API_RATE_LIMITED`, `API_TRANSIENT_ERROR`, `API_NETWORK_ERROR`                                                                                                                       | A call failed after its retries.                                                                                                                                                                 |
| `DEFINITION_LIST_TRUNCATED`, `ALLOWLIST_UNREADABLE`, `ALLOWLIST_STALE`, `ALLOWLIST_ENTRY_UNUSABLE`, `ALLOWLIST_SHADOWS_SANDBOX`, `IMPORT_UNVERIFIED`, `LOCAL_SHADOWS_SANDBOX_MODULE` | Advisories, on stderr. The exit code stays `0`.                                                                                                                                                  |

Every compiler code — `IMPORT_UNAVAILABLE`, `SYMBOL_COLLISION`, `IMPORT_CYCLE` and the rest — appears as a
definition's `compile_error` rather than as a failure. It is the same code you would get from `build`, reported
instead of raised.

Every code above, with its remediation, is in [Error codes](/cli/error-codes).

## See also

* [`blobhub workflow deploy`](/cli/workflow/commands/deploy) — what `diff` is describing.
* [`blobhub workflow pull`](/cli/workflow/commands/pull) — how a `REMOTE_EDIT` state gets onto disk.
* [`blobhub blob show`](/cli/blob/show) — when `diff` itself cannot resolve the blob or revision.
* [Concepts](/cli/concepts) — managed code ports and the three states behind `state`.
