Skip to Content
AgentsTroubleshooting

Troubleshooting

Gavana’s failures are designed to be readable. Start with the exit code, then the error code, then this page.

First: run doctor

gavana doctor --output human

Four checks, exit 0 only if all pass:

CheckFails whenFix
nodeNode.js major version is below 20Install Node.js 20 or newer
credentialsNo token in the config file, environment, or command linegavana auth login, or set GAVANA_AGENT_TOKEN
base_urlNot HTTPS, and not HTTP on a loopback addressUse https://app.gavana.ai, or http://localhost:3000 for local work
authenticationGavana rejected the credentialRead the reported code and requestId, then see below

Then confirm what the credential can actually do:

gavana auth status --jq '.scopes[]' -r

Half the “the agent cannot do X” reports are a missing scope, visible in one command.

Exit codes

ExitMeaningFirst thing to check
0Success—
1Any other failure, including a failed doctorThe stderr error object
2Usage, configuration, or validation errorThe fields array — it names what to fix
3Authentication or authorization errorToken validity, then scopes
4Revision or idempotency conflictRe-read the canvas
5Not foundThe handle, and whether a Run or Job record expired
7Network errorConnectivity and the base URL
8Wait timeout — the work is still runningResume with run wait or job wait
9A waited Run or Job ended failed, canceled, or expiredThe failure.code on stdout

Exit 8 and exit 9 are different failures. 8 means you stopped waiting — the work continues and the handle is still good. 9 means the work itself reached a terminal failure. Never treat 8 as a reason to start a second paid run.

Error codes

Errors are JSON on stderr. The code is stable; match on it rather than on the message.

CodeSourceMeaning
usageLocalThe command is malformed. Nothing was sent
configurationLocalMissing token, missing or invalid base URL, or a runtime without fetch
validation / input_validation_errorServer 400/422The request was understood and rejected. Read fields
unauthorizedServer 401The credential is not valid
forbiddenServer 403Valid credential, insufficient scope
not_foundServer 404No such handle, or the record expired
conflictServer 409Revision or idempotency conflict
rate_limitedServer 429Too many requests
upstreamServer 5xxA Gavana or provider-side failure
networkLocalGavana could not be reached
timeoutLocalThe request or the wait timed out
canceledLocalThe wait was aborted
invalid_responseLocalGavana returned something unparseable
internalLocalAn unexpected client-side failure

Authentication and permissions

unauthorized — “Gavana could not verify this agent token”

The token does not exist, was revoked, or was mistyped. Create a new one and reconfigure the client.

“This agent token expired. Create a new token in Gavana.”

Tokens default to Never expiry. This message applies only when a token was created with a custom 1–90 day expiry; check Personal Access Tokens and create a replacement.

“The delegated Firebase session was revoked. Create a new agent token.”

The Gavana account’s own session was invalidated — usually by signing out everywhere or a password change. Every token created from that session stops working. Sign in again and create new tokens.

forbidden — the token is valid but lacks the scope

gavana auth status --jq '.scopes[]' -r

Match what you are calling against what the operation needs:

OperationScopes
canvas list/get/render, node get, recipe search/get, action list/getcanvas:read
canvas apply with validateOnlycanvas:read
canvas apply, node and connection writes, canvas createcanvas:read, canvas:write
asset list/getasset:read
element list/get/history, element collection-listelement:read
Element and Element-collection mutationselement:read, element:write
asset uploadasset:read, plus image:generate or video:generate
model list/get, provider listimage:generate or video:generate
recipe forkcanvas:read, canvas:write
action runcanvas:read, canvas:write, asset:read
image generate/edit/variations, recipe runcanvas:read, canvas:write, asset:read, image:generate; add element:read when applying Elements
video generatecanvas:read, asset:read, video:generate
run get/wait/cancel, job get/wait/cancel, video downloadjob:manage

Two that surprise people:

  • Listing models and providers needs a generation scope. A read-only token cannot browse the model catalog.
  • asset upload needs a generation scope, and action run with a local file input needs it too — because the local file is privately uploaded before the Action runs. Existing node: and asset: handles do not.

Scopes cannot be added to an existing token. Create a new one.

“Waiting for the Run always fails”

The token lacks job:manage. Either create a token that includes it, or switch to --no-wait plus a signed webhook. See Webhooks.

Conflicts

conflict on a canvas write

Someone changed the canvas between your read and your write. The response includes the current revision.

Do not re-send the same operations with the newer revision — that is the overwrite the check exists to prevent. Read again, look at what changed, rebase your addition around the newer graph, and use a new idempotency key if the payload changed.

“Automatic image targets are incomplete”

You reused an idempotency key whose earlier attempt created only some of the automatic target nodes. Use a new key, or pass explicit --target node:NODE_ID values.

The same command created a second canvas

With --destination new-canvas, the idempotency key determines the canvas identity — the new canvas id is derived from a hash of the key. A different key means a different canvas. Reuse the key to reuse the canvas.

Not found

A run: or job: handle stopped resolving

Image and Action observation records expire: 24 hours while unacknowledged, 15 minutes after the first successful server finalization, and seven days for the expired tombstone. Recipe Run records currently do not use that temporary TTL.

The node: and asset: handles from the result stay durable. If you kept only the run: handle, read the destination canvas to recover the outputs.

A handle that “should” exist does not

If it was constructed rather than copied from a response, that is the bug. Handles come from Gavana. For a shared asset, use the owner-qualified asset:OWNER_UID:ASSET_ID form exactly as returned.

Usage errors

Message patternFix
recipe run requires --destination …Supply agent-canvas, new-canvas, or a canvas handle
… with --destination new-canvas requires --canvas-titleAdd --canvas-title
Unknown Recipe input KEYRun recipe get and use the exact keys
Recipe input X was provided more than onceOne --input per declared input
<Action> requires exactly N --input valuesMatch the count and order from action get
--last-frame requires --first-frameSupply a first frame
--canvas canvas:<id> is required when a video frame or reference uses node:<id>Add --canvas
Video --reference values must be uniqueRemove the duplicate
--download cannot be combined with --no-waitQueue, then video download
--duration must be a whole number from 1 through 120Use an integer the model also supports
<Model> does not support --XCheck model get for the model’s real parameters
<Model> does not support --X Y. Supported values: …Use one of the listed values
Video model X exists on more than one connectionUse the model: handle from model list
Output file already exists: …Add --yes to overwrite, or choose another path
Reference image must be a PNG, JPEG, WebP, or GIFConvert it. The CLI checks magic bytes, not the extension
Reference image is too largeMaximum 50 MB per image
An image job can use up to 16 referencesReduce the count; video allows nine
--jq must be a jq-style pathStart with .; only property paths, non-negative indexes, and []
-r/--raw cannot be combined with --output …-r works only with json or raw
node update needs --json/--file or a title, content, prompt, position, or size optionAn empty patch is refused rather than written
… Re-run with --yesThe command is destructive and wants confirmation

The retired campaign commands

The retired campaign command surface is disabled. Use Recipe Library forks and explicit canvas/image commands instead.

Expected. The campaign group is retired. Set GAVANA_ENABLE_LEGACY_CAMPAIGN_COMMANDS=true only to migrate off it. Build on Recipes instead.

Output problems

“My script cannot find .result any more”

You added --jq. With --jq the CLI prints only the selected value, without the ok/result envelope. Pick one shape and keep it.

“The output is empty”

With -r/--output raw, an empty array prints nothing at all. With --output jsonl, an empty result set prints nothing. Both are success, not failure — check the exit code.

“Progress output is polluting my pipeline”

It is not. Progress and errors go to stderr; only the result goes to stdout. If your pipeline sees them, something is merging the streams.

Connection problems

network — “Could not reach Gavana at …”

Check connectivity and the base URL:

gavana config get --jq '.baseUrl' -r

Safe GET requests are retried up to three times on transient errors and on 429, 502, 503, 504, respecting Retry-After. Writes are never retried automatically. A network error on a write means you must decide, with the user, whether to try again.

configuration — “Gavana base URL must use HTTPS, except on local loopback”

Remote origins require HTTPS. Plain HTTP is accepted only for localhost, 127.0.0.1, and ::1. The base URL also cannot contain credentials, a query string, or a fragment.

The wrong environment or account

gavana config list gavana config use work

Precedence, highest first: --profile, then GAVANA_PROFILE, then CRAFTBOARD_PROFILE, then the stored active profile, then default.

Browser login never completes

It times out after three minutes. On a remote or headless machine use --no-browser and open the printed URL yourself, or use a Personal Access Token via --token-stdin.

Configuration problems

“The CLI cannot find my token”

Resolution order for the config path:

  1. GAVANA_AGENT_CONFIG_FILE
  2. CRAFTBOARD_AGENT_CONFIG_FILE
  3. $XDG_CONFIG_HOME/gavana/agent.json
  4. ~/.config/gavana/agent.json

A legacy ~/.config/craftboard/agent.json is read as a fallback when no explicit config file is set.

macOS Keychain

On macOS the token is stored in Keychain under the service ai.gavana.cli, keyed by profile name, and the config file records credentialStore: "macos-keychain" instead of a token. Keychain is skipped when GAVANA_CLI_KEYCHAIN=false, when a custom config file path is set, or when XDG_CONFIG_HOME is set — in those cases the token goes into the mode-0600 file.

If Keychain access is denied, the token is unreadable and credentials fails in doctor. Log in again, or set GAVANA_CLI_KEYCHAIN=false and log in to use the file instead.

“I cannot log out”

auth logout revokes an OAuth token at Gavana before removing the profile, so it needs network access. A token-based profile has nothing to revoke remotely — revoke it in Personal Access Tokens.

MCP problems

SymptomCauseFix
The server never appears in the clientRegistered at the wrong scope, or the config was not reloadedRe-register at user scope; restart the client
The local server will not startNode.js older than 20, or npx unavailableInstall Node.js 20+
Every call is unauthorizedToken was revoked, mistyped, or created with a custom expiry that has passedCreate a new token and update the client configuration
Tools are listed but everything is refusedRead-only mode, or a read-only tokenRemove GAVANA_MCP_READ_ONLY, or use a token with the scopes you need
No webhook option anywhereBy designMCP omits webhook secrets. Use the CLI or the API
gavana mcp install codex fails immediatelycodex is not on PATHInstall it, or use gavana mcp config codex and register by hand

More: MCP troubleshooting.

Agent behaviour problems

These are not error codes. They are the agent going off contract.

SymptomCauseFix
The agent reorganised a canvas when asked to add to itIgnored the preservation contractPoint it at the safety contract. “Add” never means “reorganise”
The agent generated something nobody approvedInferred authorisation from preparation languagePreparing is not running. Approval is per-turn
Two charges for one requestAutomatic retry after a failureNever auto-retry paid work. A retry is a new intent
The agent reported a canvas node that does not existClaimed durability it never observedRead back before reporting. Report only what the server returned
The agent invented a model: handleGuessed instead of listingmodel list then model get. Handles are opaque

What to send support

Send the requestId, the operation by name, and the error code.

gavana canvas get canvas:OWNER_UID:CANVAS_ID 2>&1 >/dev/null | jq '.error | {code, status, requestId}'

Every Gavana response carries an X-Request-ID, surfaced as requestId on errors and — where the server did not already supply one — on successful results too. It identifies the exact server-side trace without exposing anything else.

Never send a token, a full request body, a config file, or a terminal screenshot. The requestId is enough, and everything else is a leak. If a credential has already been shared, revoke it now.

Last updated on