# Error reference

> Every Graft error code, what it means, and how to recover. 51 codes.

Look up an error code you saw in a terminal, a log, or an agent's tool result.

Every Graft error has three parts: a `code` from this list, a `message` saying
what happened, and a `fix` naming the next step. The `fix` in your terminal is
specific to your failure. The entry on this page explains the general cause, so
you can tell whether the fix applies or something else is wrong.

Agents can read the same entries without leaving their tools: the
`explain_error` MCP tool returns them for any code.

There are 51 codes, listed alphabetically.

## Codes

### ALREADY_INITIALIZED

`graft init` was run in a directory that already has a graft.config.

**Usually because**

- Re-running init in an existing project
- Pointing init at the wrong directory

**How to recover**

This is already a Graft project — evolve it by editing the existing graft.config.ts (add collections or fields there), or run `graft init <dir>` against an empty directory.

### APPROVAL_INVALID

An approval id was supplied (the `approval` argument over MCP, the x-graft-approval header over raw HTTP), but it cannot authorize this call — details.reason says why (pending, denied, already_consumed, mismatch, or not_found).

**Usually because**

- Retrying before a human has decided (pending)
- Reusing an approval — they are one-shot (already_consumed)
- Changing the input or function between request and retry (mismatch)
- A human refused the operation (denied)

**How to recover**

pending → wait for the human decision; denied → do not retry, ask the operator; already_consumed or not_found → call again without the approval id to file a fresh request; mismatch → retry with exactly the approved input.

### APPROVAL_SELF_DECISION

The identity deciding an approval is the same identity that requested it. Separation of duties: a requester can never approve (or deny) their own destructive operation.

**Usually because**

- An agent (or a wrapper acting for it) running `graft approve` on an approval it filed itself
- Passing the requester's identity as the decider (e.g. reusing the same dev-token id)

**How to recover**

A DIFFERENT operator must review it: they run `graft approve <id>` (or `graft deny <id>`) under their own identity. Do not retry as the requester and do not switch identities to impersonate a reviewer — the gate exists so a second party sees the exact function + input before it runs.

### APPROVAL_UNATTRIBUTED

The approval was filed by a caller with no stable identity, so there is nobody the request can be held to and no meaningful separation of duties to enforce. It cannot be decided by anyone.

**Usually because**

- An unauthenticated caller reached a destructive function (an MCP mount or functions route serving anonymous actors)
- A trusted issuer minted a token with no `sub` claim, so the actor authenticated but carried no id
- A row left over from before approvals required an identified requester

**How to recover**

Fix the filing surface, not the row: require authentication where the approval was requested (an actor resolver, or a token that carries `sub`), then have the caller request the operation again so the approval records who wants it. A stale row can be resolved directly in the database.

### ASSET_EXISTS

An asset upload targeted a key that already holds a binary. The store has no version history, so an overwrite would irreversibly replace it — uploads refuse unless overwrite is explicit.

**Usually because**

- Re-uploading with the same key instead of picking a new one
- Two documents' assets colliding on a generic key like assets/hero.png

**How to recover**

Pick a distinct key (e.g. prefix it with the document: pages/pricing/hero.png) — that is almost always right. Only pass `overwrite: true` (put_asset) or `--overwrite` (graft asset put) when replacing the existing binary is the actual intent; every document referencing that key will show the new bytes.

### AUTHORITY_MISMATCH

An operation used the wrong interface for a collection's authority: files/write_content on a db-authoritative collection, or record helpers on a file-authoritative one.

**Usually because**

- Authoring an MDX file for a collection whose rows live in Postgres
- Calling insertRecord/listRecords on a collection whose documents are files

**How to recover**

Check the collection's authority with describe_schema. File-authoritative → author MDX (write_content / files + compile). Db-authoritative → go through its functions (POST /api/fn/\<name>); the rows are operational data Postgres owns.

### BRANCH_BACKEND_FAILED

The branch backend's control plane (the Neon API for `neon` branches) rejected or failed an operation, so the branch's physical state may not match the registry.

**Usually because**

- An invalid or expired NEON_API_KEY, or one scoped to a different project
- A wrong GRAFT_NEON_PROJECT_ID
- Neon-side limits (max branches) or a transient API outage
- The branch's compute endpoint never became reachable after create

**How to recover**

Check NEON_API_KEY and GRAFT_NEON_PROJECT_ID in the environment, then retry. If a create failed partway, the error says whether a Neon branch was left behind — delete it in the Neon console (or retry the drop) before recreating. Overlay branches never hit this: they need no external API.

### BRANCH_EXISTS

A branch with that name is already registered.

**Usually because**

- Re-running a create for a branch that already exists
- Two previews competing for the same name

**How to recover**

Use the existing branch, pick a different name, or drop the existing one first. Registering a branch is idempotent only if you check first — names are unique.

### BRANCH_INVALID

A branch operation was rejected because the name or the topology change is not allowed.

**Usually because**

- A branch name that is not URL-safe (uppercase, spaces, punctuation)
- Making a branch its own parent, or dropping main
- Dropping a branch that still has child branches

**How to recover**

Use lowercase kebab names with optional "/" segments (e.g. "preview/checkout"). Fork previews from main; drop child branches before their parent; main is the root and cannot be dropped.

### BRANCH_NOT_FOUND

An operation referenced a branch that is not registered in the topology.

**Usually because**

- A typo in the branch name
- Forking from or dropping a branch that was never created
- Expecting a branch to exist that a teammate has not created yet

**How to recover**

List the registered branches (graft branch) to see valid names. "main" is always seeded; create previews off it before forking from or merging them.

### COLLECTION_NOT_FOUND

Content referenced a collection that is not registered in the schema.

**Usually because**

- A typo in the collection name
- A document placed in a folder that has no matching defineCollection
- The collection exists in code but was not passed to compile()/the MCP server

**How to recover**

Call list_collections to see what is registered. Either use one of those names, move the file into a registered collection folder, or add a defineCollection for it in graft.config.ts.

### COMMIT_FAILED

git refused to record the commit. The working tree is untouched by the refusal.

**Usually because**

- No committer identity configured (user.name / user.email)
- A pre-commit or commit-msg hook rejected the change
- Nothing to commit — the selected paths match the last commit already
- The repository is mid-merge or mid-rebase

**How to recover**

The error's `details.stderr` carries git's own words. For an unset identity, run `git config user.name "…"` and `git config user.email "…"`. Selected files may already be staged; `git status` shows the current state, and committing from a terminal always remains available.

### CONFIG_INVALID

graft.config exists but could not be loaded, or it does not export a valid `collections` record.

**Usually because**

- A syntax or import error in graft.config.ts
- Missing `export const collections = { … }`
- A collections entry that was not created with defineCollection

**How to recover**

Fix graft.config.ts so it imports defineCollection/field from @usegraft/core and exports `collections` as a record of defineCollection results — the error message names exactly what failed to load or validate.

### CONFIG_NOT_FOUND

No graft.config.\{ts,js} was found in the working directory or any parent.

**Usually because**

- The command was run outside a Graft project
- The project has not been initialized yet

**How to recover**

cd into the project root (the directory holding graft.config.ts) and retry, or scaffold a new project with `graft init`.

### CONTENT_CONFLICT

A write or a publish was refused because the document changed underneath it: someone (a person in Studio, an agent over MCP, a developer's push) saved a newer version after this one was read. Nothing was written.

**Usually because**

- Two people, or two browser tabs, editing the same document
- An agent called write_content on a document a person had open in Studio
- A publish where the production branch changed the same document since the draft began

**How to recover**

Read the document again (`details.path`) and reapply the change on the current version. For a publish, `details.conflicts` lists each document; publish again choosing, per document, to keep the draft version or take the published one.

### CONTENT_DIR_NOT_FOUND

The configured content directory does not exist on disk.

**Usually because**

- The project has no content/ directory yet
- compile() was pointed at the wrong path

**How to recover**

Create the directory (documents live at \<contentDir>/\<collection>/\<slug>.mdx) or correct the contentDir passed to compile()/the MCP server. write_content creates folders automatically.

### CONTENT_REFERENCE_NOT_FOUND

A reference field names a document that does not exist. graft compile stops on it, and a database-sourced publish refuses it, including the deletion of a document that live documents still point at.

**Usually because**

- The referenced document was deleted, renamed or given a new slug
- A slug typo in the reference
- The reference and its target were published separately, target last

**How to recover**

Read details.references: each names the file, the field and the missing collection/slug. Create the missing document, or point the field at one that exists (list_content shows what exists). To delete a document others point at, change them in the same publish. graft check lists every broken reference at once.

### CONTENT_SOURCE_MISMATCH

The operation works on one source of content, and this project uses the other. A database-sourced project keeps its content in Postgres, so operations that read or rewrite the files in git (reverting to a commit, merging a branch's files, running a content migration over the files) have nothing to act on.

**Usually because**

- graft.config.ts exports `content = { source: "database" }` and the call reached for files
- A script written for a git-sourced project ran against a database-sourced one

**How to recover**

Use the database-sourced equivalent the error's `fix` names. To change a document, write it with write_content and publish it with publish_drafts. Do not edit the files under the content directory and expect the site to change: they are a checkout, not the source.

### CONTENT_TREE_READ_ONLY

A write reached the content tree, but the filesystem refused it. Authored content is files, so writing requires a writable checkout — serverless platforms deploy a read-only filesystem.

**Usually because**

- Studio or an MCP write served from a serverless deployment (Vercel, Netlify, Cloudflare)
- A container with the project mounted read-only
- File permissions on the content directory

**How to recover**

Run the writing surface where the checkout is writable — local `graft studio` / `graft mcp`, or a self-hosted container with the project mounted read-write — and let the deployment serve reads only. Writes then arrive as git commits, which is the model: git is authoritative for authored content.

### DESTRUCTIVE_OP_REQUIRES_APPROVAL

The operation is human-gated: a pending approval request was filed (details.approvalId) and the call will not run until a human approves it. Destructive functions are always gated; under the 'human' approval policy every mutation is.

**Usually because**

- Calling a function marked `destructive: true` (deletes or irreversibly overwrites data)
- Calling any mutation on a deployment whose approvalPolicy is 'human'
- Calling a destructive function over MCP at all — the 'unattended' policy that lifts this gate for a headless deployment is not part of the MCP surface, so there is no server setting that makes this tool call stop asking. That is deliberate, and not something to route around

**How to recover**

Ask a human operator to run `graft approve <approvalId>` (they can also `graft deny` it). Once approved, retry the EXACT same call carrying the approval id — over MCP pass it as the `approval` tool argument; over raw HTTP send the `x-graft-approval: <approvalId>` header. Approvals are one-shot and bound to the exact input — never work around the gate. If you are seeing this over MCP, the server did not ask your client directly: either the mount has not opted into elicited approvals (`approvalElicitation`, off by default and intended for a local server whose operator is present) or your client did not declare the elicitation capability. Both are ordinary — the gate is the same either way, only the way the human is reached differs.

### DOCUMENT_NOT_FOUND

No document with the requested slug exists in that collection's files.

**Usually because**

- A slug typo
- The document lives in a different collection
- The document was deleted (git is authoritative — the files are the truth)

**How to recover**

Call list_content for the collection to see every slug that exists, then retry with a real one — or author the document with write_content.

### ENV_VAR_MISSING

A required environment variable is not set.

**Usually because**

- The project has no .env file yet
- The variable exists in a different environment (e.g. CI but not local)

**How to recover**

Add the variable named in `details.variable` to the project's .env (the CLI walks parent directories to find one) or export it in the environment, then retry.

### FRAMEWORK_VERSION_UNSUPPORTED

An adapter API needs a newer version of the framework than the app has installed. The rest of the adapter still works.

**Usually because**

- Calling `updateContent` from @usegraft/sdk-next on Next.js 15. It needs `updateTag`, which Next.js added in 16
- Mounting `graftHandler` from @usegraft/sdk-nitro on an h3 older than 1.15. It reads the request from `event.req` (h3 2) or `event.node.req` (h3 1)

**How to recover**

Use the fallback the error's `fix` names. For `updateContent` that is `revalidateContent(branch, changes)`, which works on Next.js 15 and 16. For `graftHandler` outside Nitro, call the Graft handler with a Web Request directly. Or upgrade the framework.

### FUNCTION_EXECUTION_FAILED

The function's handler threw an unexpected (non-Graft) error — a bug in the handler or its environment, not in the caller's input.

**Usually because**

- A runtime error inside the handler code
- The database is unreachable or a required env var is missing on the server

**How to recover**

Retrying with the same input will fail again. Find the invocation in the server logs via `details.correlationId`, fix the handler code or the server environment, then retry.

### FUNCTION_NOT_FOUND

The invoked function name is not registered in the functions runtime.

**Usually because**

- A typo in the function name (the last path segment of the POST URL)
- The function exists in code but was not included in the exported `functions` record

**How to recover**

The error's `details.available` lists every registered function — call one of those, or add a defineFunction to graft.config.ts and include it in the exported `functions`.

### GIT_UNAVAILABLE

An operation needed git and could not reach it: either the `git` binary is not on PATH, or the content directory is not inside a git work tree.

**Usually because**

- The project was scaffolded but `git init` was never run
- Studio or the CLI running in a container image that ships no git binary
- The content directory lives outside the repository (a mount, a symlink target)

**How to recover**

Run `git init` at the project root and make a first commit, or install git. Nothing else in Graft requires it — content still compiles and serves — but the change history, `graft compile`'s recorded SHA, and reverting all depend on it.

### INDEX_OWNERSHIP

A projection would soft-delete every document in collections this project's schema doesn't know — the signature of two projects pointing at one DATABASE_URL. Nothing was written; the transaction rolled back.

**Usually because**

- DATABASE_URL points at another project's database (e.g. a shared repo-root .env)
- A collection was renamed or deleted in graft.config.ts, so its old rows look foreign

**How to recover**

Each project needs its own database or branch: set DATABASE_URL in a .env next to graft.config.ts (it overrides parent .envs). If the schema really did drop or rename a collection, a human runs `graft compile --prune-unknown` once — the override is CLI-only by design.

### INPUT_VALIDATION_FAILED

A function invocation's input does not satisfy the function's Zod input schema.

**Usually because**

- The request body is not a JSON object
- A required input field is missing or has the wrong type

**How to recover**

Fix the fields listed in `details.issues` and retry. describe_schema shows each function's exact input fields; an empty body is treated as \{}.

### INTERNAL_ERROR

The graft CLI hit an error it does not recognize. It is a bug in Graft or in code the command loaded, not a mistake in how the command was called.

**Usually because**

- A bug in Graft
- graft.config.ts or a module it imports throwing an ordinary Error

**How to recover**

Run the command again with GRAFT_DEBUG=1 to print the stack trace. If it points into graft.config.ts or your own code, fix it there; otherwise report it with the trace.

### INVALID_SLUG

A slug is not URL-safe kebab-case.

**Usually because**

- Uppercase letters, spaces, or punctuation in the slug or filename
- Leading/trailing/double hyphens

**How to recover**

Use lowercase letters, digits, and single hyphens only (e.g. "getting-started"). Set a valid `slug` in frontmatter or rename the file.

### METHOD_NOT_ALLOWED

The endpoint was called with an HTTP method it does not serve.

**Usually because**

- GETting a function endpoint (functions are RPC over POST)
- A client following a redirect that downgraded the method

**How to recover**

Use the method named in the `Allow` response header — for Graft functions, POST a JSON object body to the same URL.

### MIGRATION_FAILED

A content or data migration could not be applied; nothing was written (runs are all-or-nothing).

**Usually because**

- The transform's output does not satisfy the collection's current schema
- The transform threw for some documents/rows
- A file's frontmatter is not parseable YAML

**How to recover**

Read details.failures — each entry names the file/row and why it failed. Fix the transform (or the listed files) in migrations/\<id>.ts, then re-run `graft migrate --apply`. Dry-run first with `graft migrate` to see what would change.

### MIGRATION_REQUIRED

The schema and the stored content/data have drifted; a migration must run first.

**Usually because**

- graft.config.ts changed shape while documents still use the old shape
- A database schema change is pending

**How to recover**

Run the pending migration (content migrations update the files; DB migrations via @usegraft/db), then retry the operation.

### NEEDS_DATABASE

This project runs on the static storage engine (index = "static" in graft.config), and the requested feature needs the Postgres engine: db-authoritative collections, typed functions, or database branching.

**Usually because**

- A db-authoritative collection or a defineFunction was added to a static-mode project
- graft compile --branch \<name> was run in static mode (branches are git branches there)

**How to recover**

Either stay static (content-only: use git branches for previews) or upgrade: set DATABASE_URL in .env and switch graft.config to `export const index = "postgres"`, then re-run `graft compile`.

### NOT_IMPLEMENTED

The capability is planned but not built yet.

**Usually because**

- Calling a placeholder API from a later phase

**How to recover**

Use the documented alternative (the error's `fix` names it if one exists), or accomplish the task by editing files directly — git is always a valid interface.

### PAYLOAD_TOO_LARGE

The request body is larger than the endpoint accepts. It was refused before Graft read the rest of it, so the function or tool never ran. A refused function call is still recorded against the caller's rate limit.

**Usually because**

- A function call or Studio save carrying more than 1 MiB of JSON, the default for those routes (Studio asset uploads allow 14 MiB)
- An MCP `put_asset` with a file over about 24 MB, which is 32 MiB once base64-encoded
- A client sending an unbounded stream to an endpoint

**How to recover**

Send a smaller body. For a large asset, upload it with `graft asset put <file>`, or pass `path` to `put_asset` on a local stdio server; over HTTP, `put_asset` takes base64, which counts against the limit. Each endpoint has its own limit: a direct function call or Studio request raises `maxBodyBytes` on that handler, and an MCP message, including `run_function` arguments and HTTP `put_asset`, raises it on the MCP handler. `--max-body-bytes` on graft serve sets all of them.

### RATE_LIMITED

This caller has invoked the function more times than its per-window limit allows. Every attempt counts, including rejected ones.

**Usually because**

- A retry loop hammering a function after failures
- Many calls from one actor (or one IP, for anonymous callers) in a short window

**How to recover**

Wait out the window (the Retry-After header says how long) before retrying, and fix whatever caused the burst — the limit is per caller per function, so backing off actually works.

### REGISTRY_FILE_EXISTS

`graft add` would overwrite a file that already exists in the project, so it wrote nothing (adds are all-or-nothing).

**Usually because**

- The item (or one of its dependencies) was already added
- A project file happens to share a target path with the item

**How to recover**

Inspect the listed file(s). If replacing them is intended, re-run with `--overwrite`; otherwise move/rename your file first. `graft add --dry-run <name>` previews every path an item would write.

### REGISTRY_ITEM_INVALID

A registry item's manifest is malformed, or the item requires a different @usegraft/core version than the one installed.

**Usually because**

- registry.item.json does not match the manifest schema
- The item's `graftVersion` range does not include the installed core version
- A file the manifest lists is missing from the item directory

**How to recover**

The error names what failed (a manifest field or the version mismatch). For a version mismatch, move @usegraft/core to the range the item needs; a malformed manifest is a registry bug — fix the item or report it.

### REGISTRY_ITEM_NOT_FOUND

`graft add` was asked for a registry item (a copy-in primitive) that does not exist.

**Usually because**

- A typo in the item name
- Expecting a community/remote item — only the bundled core registry ships today
- The item was renamed or removed

**How to recover**

The error's `details.available` lists every item you can add. Run `graft add <name>` with one of those, or build the primitive by hand as owned code under graft/.

### REMOTE_STORE_FAILED

The content store that writes to a remote repository (GitHub) refused or failed a request. When GitHub refused (`details.status` is set), that request was not applied, but an operation made of several requests may have finished earlier steps: a publish can move the production branch and then be refused while tidying the draft. When a request failed before an answer arrived (a network error), GitHub may still have applied it. Either way, check the draft and the production branch before retrying.

**Usually because**

- The token or GitHub App lacks Contents or Pull requests write access to the repository
- GRAFT_GITHUB_REPO or GRAFT_GITHUB_BRANCH names a repository or branch that does not exist, or the credential cannot see a private repository (GitHub answers 404 for both)
- A branch protection rule forbids direct pushes to the production branch
- GitHub rate limiting or an outage

**How to recover**

Read the error message and `details`. A GitHub refusal carries `details.status` and `details.message`, GitHub's own answer; a failure before GitHub answered (network, a malformed response) has the message only. A 401 or 403 is credentials or permissions; a 404 is the repository or branch name, or a private repository the token or GitHub App is not installed on; a protected branch needs `GRAFT_STUDIO_PUBLISH=pull-request`. Retry once the cause is fixed: drafts already saved stay on their branch. After a network failure, reload the entry or the list of changes first, since the request may have gone through.

### REMOTE_UNREACHABLE

The graft CLI could not reach the hosted site it was pointed at, or the site answered with something other than Graft.

**Usually because**

- A typo in the URL given to graft login or in GRAFT_URL
- The site is down, or this computer has no network
- The site does not run graft serve, so it has no /api/mcp or /api/studio/v1

**How to recover**

Check the address with `graft auth status`, open it in a browser, and log in again with `graft login <url>` if it changed. Studio is served by `graft serve --studio`.

### REVALIDATE_FAILED

The content was written and the index is up to date, but telling the app to refresh its cache failed. The app may serve the old copy until its next refresh.

**Usually because**

- GRAFT_WEBHOOK_SECRET differs from the secret the app's revalidate route checks (the route answers 401)
- GRAFT_REVALIDATE_URL is unreachable from the server, redirects, or points at the wrong path
- An onContentChange listener passed to an in-app mount threw

**How to recover**

Do not retry the write: it succeeded. Fix the cause the error's `fix` names. Then POST the write's `{ branch, gitSha, changes }` to the revalidate route: an agent's result carries `changes`, and the server logs the whole body with the error. Do not recompile instead. The index already holds the change, so a later compile reports these documents as unchanged and refreshes none of them.

### ROUTE_NOT_FOUND

The Graft server (`graft serve`) has nothing mounted at the requested path — the request reached the right process but the wrong URL.

**Usually because**

- A typo in the endpoint path (e.g. /api/fns instead of /api/fn/\<name>)
- Expecting the frontend app's routes on the headless runtime — graft serve hosts functions, MCP, authored-content reads, and health

**How to recover**

Use POST /api/fn/\<name> for typed functions, POST /api/mcp for the MCP Streamable HTTP surface, GET /api/content/v1/documents or GET /api/content/v1/search for authored content, or GET /healthz for liveness. The error's details carry the path that missed.

### SCHEMA_VALIDATION_FAILED

A document's frontmatter does not satisfy its collection's Zod schema.

**Usually because**

- A required field is missing from the frontmatter
- A field has the wrong type (e.g. a string where a number is expected)
- The schema changed in graft.config.ts and existing documents were not updated

**How to recover**

Call describe_schema to see the exact fields the collection expects, fix the frontmatter (the error's `details.issues` lists every violation), then retry the write or re-run compile.

### SLUG_NOT_UNIQUE

Two documents in the same collection resolve to the same slug.

**Usually because**

- Two files share a filename-derived slug
- A frontmatter `slug` duplicates another file's slug

**How to recover**

The error's details list both files. Give one of them a unique slug (frontmatter `slug:` wins over the filename) and re-run compile.

### STATIC_INDEX_NOT_FOUND

A read tried to open the static index artifact (.graft/index.db by default), but the file does not exist — the project has not been compiled yet.

**Usually because**

- graft compile has never run in this checkout
- The artifact path in graft.config's `index` setting does not match where compile wrote it
- A deploy shipped the app without running graft compile in the build step

**How to recover**

Run `graft compile` (or add it before the framework build in the deploy's build command). The artifact must be deployed with the app.

### STATIC_INDEX_UNSUPPORTED

Static index mode needs the node:sqlite built-in with FTS5, which this Node runtime does not provide (FTS5 ships in the bundled SQLite only from Node 22.16).

**Usually because**

- Node older than 22.16 running the CLI or the app server

**How to recover**

Upgrade Node to 22.16+ (24 LTS recommended), or switch the project to the Postgres index (DATABASE_URL + `export const index = "postgres"`).

### TOKEN_INVALID

A bearer token was sent but could not be verified — different from sending no token (which makes the caller anonymous).

**Usually because**

- An expired or not-yet-valid JWT
- A token issued by an issuer this deployment does not trust
- A wrong audience claim, a bad signature, or a malformed token

**How to recover**

Mint a fresh token from a trusted issuer (details.reason states what failed). Do not strip the Authorization header to fall back to anonymous — fix the token instead.

### UNAUTHORIZED

The caller's token does not permit this operation.

**Usually because**

- A missing or expired agent token
- A token scoped to reads used for a write
- An anonymous call to a mutation that is not marked `public: true` (the secure default)

**How to recover**

Obtain a token with the required scope from the project owner; do not retry with the same credentials. If the function is meant to accept anonymous callers, its definition needs `public: true`.

### USAGE_INVALID

A graft command was called with an unknown command, flag or subcommand, a missing argument, or a flag without its value. Nothing ran.

**Usually because**

- A typo in a command or flag name
- A flag that belongs to a different command
- A required argument left out, such as the branch name for graft merge

**How to recover**

Read the `fix`, which names the closest match when there is one, then run `graft <command> --help` for the exact usage. `graft commands --json` lists every command and flag. The CLI exits 2 for this error.
