Error taxonomy & recovery
When an API fails, it can fail in a hundred different shapes. Anvil turns all of them into one. Every failure — from any generated CLI or MCP tool — comes back as a single structured envelope with a code from the fixed list below.
{ "error": { "code": "rate_limited", "message": "...", "retryable": true, "safe_to_retry": true, "operation": "...", "trace_id": "..." } }The codes on this page aren’t written by hand. They’re imported from
apps/docs/src/data/errors.json, which a real compile produces
(node scripts/gen-docs-data.mjs). A test —
packages/generators/src/docs-data.test.ts — checks that file against the
runtime’s own list of codes, so this page can’t show a code the runtime no
longer returns.
The recovery advice comes from the same place agents read: it mirrors the
reference/errors.md file that ships inside every generated skill (see
errorsRef() in packages/generators/src/skill.ts). What you read here is what
a deployed agent reads.
| Code | Retry? | Recovery rule |
|---|---|---|
validation_error | no | Fix the input; check details.missing. Do not retry unchanged. |
auth_required | no | The auth profile lacks access. Check the credential env-var names in the generated setup.md, then stop and report. |
permission_denied | no | The auth profile lacks access. Check the credential env-var names in the generated setup.md, then stop and report. |
not_found | no | The resource does not exist. Do not retry. |
conflict | no | The resource already exists or a request with the same idempotency key is in flight. Do not blindly retry. |
rate_limited | if safe | Transient. Retry only if safe_to_retry is true; the tool already retried what it safely could. |
upstream_timeout | if safe | Transient. Retry only if safe_to_retry is true; the tool already retried what it safely could. |
upstream_unavailable | if safe | Transient. Retry only if safe_to_retry is true; the tool already retried what it safely could. |
unsafe_retry_blocked | no | The runtime refused to auto-retry a non-idempotent mutation after an ambiguous failure. Check upstream state before doing anything else — the call may have landed. |
confirmation_required | with flag | Re-issue with --confirm only if the user intends the effect. Check required_flags for what to supply. |
idempotency_required | with flag | Supply --idempotency-key and re-issue. |
schema_mismatch | no | The input does not match the compiled schema for this operation. Fix the shape; do not retry unchanged. |
unsupported_operation | no | The operation is not exposed by this artifact — usually not approved, or removed at recompile. Do not retry; report it. |
policy_denied | no | A local policy blocked the call (often the ANVIL_ALLOWED_HOSTS egress allowlist — see setup.md). Stop and report. |
unknown_upstream_error | no | The upstream failed in a way the taxonomy cannot classify. Do not retry a mutation; report with the trace_id. |
Should the agent retry?
Section titled “Should the agent retry?”Two fields in the envelope answer that, and they are not the same question:
retryable— is this kind of failure temporary? A rate limit or a timeout is; a validation error is not.safe_to_retry— may this specific operation be repeated? This depends on its idempotency: an operation that is safe to repeat (calling it twice does the same thing as once) can be retried; a mutation that isn’t must not be.
An agent should act only on safe_to_retry. By the time it sees the error, the
runtime has already retried whatever it safely could, so a safe_to_retry: false
means “stop and check,” not “try harder.”