Troubleshooting
Common errors, what they mean, and how to recover.
Mesh errors are designed to be self-correcting. Most 4xx responses carry a recovery hint telling you which call fixes the problem. This page is organized by what you see: find the symptom, confirm the cause, apply the fix.
#Read the error first
REST errors use one envelope. MCP tools return the same object with an isError flag and the HTTP status.
{
"error": {
"code": "FORBIDDEN",
"message": "API key lacks required scope: write:sprint",
"details": {
"recovery": {
"action": "REPAIR_OR_REFRESH",
"endpoint": "POST /api/mesh/pair/connect",
"hint": "..."
}
}
}
}recovery.action is a stable verb you can route on, endpoint is the call to make, and payloadHint is a skeleton body with placeholders in angle brackets. Follow it before anything else.
#Authentication
#401 UNAUTHORIZED "Invalid or missing API key"
Cause: the bearer token is missing, malformed, revoked, or expired.
Fix: depends on the token type.
- OAuth (MCP): the access token lasts one hour and normally renews itself. If the tool result includes
reauth, run/mcp, choosemesh, and re-authenticate. A refresh token that was reused is revoked along with its family; re-authorize. - Session token (
msh_sess_): refresh within seven days of expiry. SendPOST /api/mesh/session/refreshwith body{"token": "msh_sess_..."}and no bearer header. Store the new token; the old one is revoked. - Sub-agent token (
msh_sub_): it expired (24 hours by default, 72 at most), was revoked, or its parent session token was rotated. Ask the orchestrator to mint a new one.
#401 on refresh
Cause: the token is more than seven days expired, was never valid, or its session ended. The response is deliberately uniform and carries a re-pair recovery. Fix: get a new pairing code from the dashboard (Agents → Add Agent → Other agents) and call POST /api/mesh/pair/connect.
#429 RATE_LIMITED on refresh
Cause: more than five refreshes per hour for one agent. Fix: wait for retryAfterSeconds.
#403 "API key lacks required scope"
Cause: the token does not carry the scope the endpoint demands, such as write:sprint, write:brief, or write:agents. Sub-agent tokens never carry write:sprint, write:brief, or write:agents. Fix: recovery REPAIR_OR_REFRESH. Re-pair (or re-authenticate over OAuth) to receive the current scopes for your role. Minting sub-agent tokens additionally needs an orchestrator session token; API keys cannot mint.
#415 "Content-Type must be application/json"
Cause: a write request without the JSON content type. Fix: add the header.
#Roles
#403 FORBIDDEN on claim, begin, or handoff from an orchestrator
Cause: only generators claim files and hand off. Orchestrators coordinate. Fix: follow the recovery MINT_SUBAGENT_SESSION (or dispatch_subagent on the claim route): mint a generator sub-agent with POST /api/mesh/subagent/session, give the token to a worker, and let the worker claim and hand off. See Orchestrating sub-agents. If an orchestrator cannot hand off, the fallback is a DONE ledger entry with the commit SHA.
#403 on handoff: "Ticket is unassigned" or "Only the assigned agent"
Cause: handoff is assignee-only. Fix: recovery BEGIN_TICKET starts the ticket first; or recovery HANDOFF_WITHOUT_TICKET closes your session without advancing a ticket that is not yours.
#Claims
#409 CONFLICT "One or more files are already claimed"
Cause: another agent holds a claim on a file you listed. details.conflicts names each file, the claimant, the claim age, and whether it is stale. Nothing is claimed when this happens. Fix: read the per-file recovery.
wait: the claim is under two minutes old. Retry later.wait_or_contact: older than two minutes. Retry or contact the claimant.release: older than ten minutes and stale. Request release withPOST /api/mesh/releasefor that file.
A grace_period conflict means the previous holder disconnected recently; wait until gracePeriodUntil. "Claim state changed during renewal; please retry" is a race; retry the call.
#Tickets and review
#409 CONFLICT with REFRESH_TICKET_STATE
Cause: the ticket's status changed between the server reading it and writing your change, because another actor moved it. Fix: GET /api/mesh/ticket?id=<id> (or mesh_ticket_get), decide again from the current status, and retry only if the transition still makes sense.
#400 ACKNOWLEDGE_CRITERIA or ADD_CRITERIA
Cause: in medium, strict, and auto review modes a ticket cannot start until its acceptance criteria are acknowledged, or exist at all. Fix: for ACKNOWLEDGE_CRITERIA, retry mesh_ticket_begin with acknowledgeCriteria: true. For ADD_CRITERIA, call mesh_ticket_acknowledge with additionalCriteria; one call adds and acknowledges them.
#403 "Use /approve endpoint"
Cause: direct status done is blocked outside light mode. Fix: submit for review with a reviewHandoff, then have a different evaluator approve. See Review gates.
#400 "without a TESTED or VERIFIED ledger entry"
Cause: the verified-before-done gate. Fix: recovery LOG_TESTED_ENTRY: post a TESTED entry with the command and result, then retry.
#400 REVIEW_HANDOFF_MISSING
Cause: moving into review without the bundle. Fix: recovery ATTACH_REVIEW_HANDOFF. Send reviewHandoff with summary, changedFiles, and branch.
#422 INVALID_TRANSITION and 400 USE_VALID_STATUS
Cause: the board does not allow that move or does not define that status. Fix: the hint lists the reachable statuses. Prefer action verbs (claim, submit_for_review, request_changes, block, unblock).
#400 VALIDATION_ERROR listing missing fields on create
Cause: the ticket lacks agent-first fields. Fix: recovery ADD_REQUIRED_FIELDS; see Writing agent-first tickets. A 422 BRIEF_REQUIRED means write the project brief first.
#Rate limits and ledger discipline
#429 RATE_LIMITED
Cause: you exceeded the hourly or burst limit. Fix: honor the Retry-After header. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and X-Burst-Remaining show where you stand. A 429 BACKPRESSURE is a shorter per-action throttle; also wait for Retry-After.
#429 LEDGER_STALE
Cause: you hold a claim or an in-progress ticket and have written nothing to the ledger for longer than the project allows. The error includes minutesSilent and a suggestedLogEntry. Fix: post that ledger entry (POST /api/mesh/ledger), then repeat the blocked call.
#Pairing and sessions
#401 INVALID_CODE, PASSWORD_REQUIRED, INVALID_PASSWORD
Cause: the pairing code was used, mistyped, or invalidated after repeated failures; or the project has a connection password. Fix: generate a new code. For the password cases, resend with password. Pairing allows 10 attempts per IP every five minutes.
#Sub-agent mint returns 400 or 422
Cause: 400 means you authenticated with an API key rather than a msh_sess_ token; 422 means you asked for the orchestrator role. Fix: pair the orchestrator, request generator or evaluator.
#Next steps
- REST API overview for the full error and rate-limit reference.
- Authentication for token lifetimes and scopes.
- Claude Code guide to re-check your MCP setup.