Skip to content
Browse documentation

Errors and exit codes

What a refusal carries, what each HTTP status means, and how to tell "the tool worked and the answer was no" from "the tool did not run".

A refusal, as the agent sees it

The SDKs raise a typed error rather than returning a status, because a wrapped tool has to interrupt the call. Carrying structure rather than a string is what lets an agent re-plan instead of retrying the same thing.

The fields on CirvixDeniedJSON
{  "name": "CirvixDenied",  "policy": "deny-dotenv-read",  "decisionId": "dec_01JQ8F2K7M",  "reason": "Reading .env files is denied outside an approved secrets flow.",  "remediation": "Request the value as a handle: secrets.get(\"STRIPE_KEY\")",  "appealable": false,  "resource": "/repo/.env.production",  "action": "fs.read"}

CirvixHeld extends it with approvers and approvalId, and is always appealable. Catch it first — it is a subclass, and a broad clause that swallows it teaches the agent that a call a person was about to approve is a failure.

Three fields, three questions

policy is which rule decided it — the name you would search for in your rule set. reason is the human sentence, for a person reading a log, and is not meant to be parsed. remediation is the legitimate path, and is frequently the field that turns a dead end into a next step.

A refusal, as the MCP client sees it

The gateway returns a denial as a **tool result**, not a transport error. A transport error tells the agent the plumbing broke, and it retries; a tool result carrying a refusal and a remediation tells it what happened and what to do instead.

HTTP statuses

StatusMeaning
200 / 201 / 204Success
400Malformed body, or a validation failure
401Missing or invalid credentials
403Authenticated, but the role is insufficient. The body carries required
404No such route, or no such resource in this tenant
409Conflict — a stale version, or an unmet governance precondition
413Body over 1 MiB
429Rate limited. The body carries retryInSeconds
503A subsystem is not configured — e.g. secrets without CIRVIX_MASTER_KEY

Statuses worth recognising

SituationStatusWhat it means
Wrong role403{ error: "Role \"member\" cannot policy:write.", required: "admin" }
Refresh token replayed401reuseDetected: true — the session family was revoked on the assumption of theft
Draft edited after approval409Approvals are bound to a content hash; editing un-approves
Reviewer requested changes409One reviewer's objection is not outvoted by later approvals
Handle spent off-path403Recorded as destination_denied, raises a high-severity alert
Directory removing the last owner409Refused so a misconfiguration cannot lock a tenant out of itself

CLI exit codes

CodeMeaning
0Success, or a permitted/held decision
1A substantive negative: findings over the gate, a deny, a broken chain, a changed replay
2Usage error, or an unhandled exception

1 and 2 are kept distinct so CI can tell "the tool worked and the answer was no" from "the tool did not run". A gate that treats those the same passes when the binary is missing.

Edit this page on GitHub
Was this page helpful?