Error codes
Stable identifiers callers depend on. The string is the contract — never rename a code, only deprecate + add a new one.
Source of truth: packages/shared/src/errors.ts. CI lint will fail if a code in this file is missing from the union (and vice-versa).
| code | class | HTTP | MCP | meaning |
|---|---|---|---|---|
auth.invalid_token | AuthError | 401 | -32001 | Agent or session token failed validation |
auth.expired_session | AuthError | 401 | -32001 | Session cookie past expires_at |
auth.missing_csrf | AuthError | 401 | -32001 | Remix action received no/invalid CSRF token |
auth.forbidden | AuthError | 403 | -32001 | Authenticated but not allowed |
auth.cross_org | AuthError | 403 | -32001 | Caller in org A acted on resource in org B |
auth.rate_limited | RateLimitError | 429 | -32007 | Per-token rate window exceeded |
validation.invalid_input | ValidationError | 400 | -32602 | Zod parse failed |
validation.bad_request | ValidationError | 400 | -32602 | Generic malformed request |
task.not_found | NotFoundError | 404 | -32004 | No task by that id in this org |
task.not_actionable | DomainError | 409 | -32000 | Task isn't in a state that allows the requested action |
task.already_claimed | ConflictError | 409 | -32005 | Another agent holds the lease |
task.lease_expired | DomainError | 409 | -32000 | Lease expired before update — re-claim required |
task.dependency_unmet | DomainError | 409 | -32000 | Upstream task not yet done |
task.invariant_violated | DomainError | 500 | -32000 | State machine got an invalid transition (bug) |
project.not_found | NotFoundError | 404 | -32004 | No project by that id, or workspace has none — details.recovery lists tools |
project.name_taken | ConflictError | 409 | -32005 | A project with that name already exists in the workspace |
project.slug_taken | ConflictError | 409 | -32005 | An explicit slug collided with an existing project |
knowledge.not_found | NotFoundError | 404 | -32004 | Knowledge entry missing or superseded |
knowledge.embedding_failed | DomainError | 500 | -32000 | OpenAI embedding call failed after retries |
quota.exceeded | QuotaExceededError | 402 | -32006 | Free-tier monthly task count exhausted |
billing.payment_required | DomainError | 402 | -32000 | Pro tier required for this feature |
not_found | NotFoundError | 404 | -32004 | Generic 404 |
conflict | ConflictError | 409 | -32005 | Generic conflict |
internal_error | DomainError | 500 | -32000 | Anything we couldn't classify |
Conventions
- Code is
<domain>.<reason>(snake_case after the dot). detailscarries extra structured fields the client may render (e.g.
{ used, limit, reset_at } on quota.exceeded, { retry_after_ms } on rate limits). Never put a stack trace there.
- Server logs the full
err.stack; the wire response goes through
DomainError.toJSON() which strips stack/cause.
- MCP JSON-RPC codes:
- -32602 validation (per JSON-RPC spec) - -32001 auth/authz - -32004 not found - -32005 conflict - -32006 quota / payment - -32007 rate limit - -32000 everything else (implementation-defined range)