Vault / wiki/201/error-handling-in-tools.md
updated 2026-05-28Error Handling in Agent Tools
How errors flow from your tools back to the model — and how to design them so the model recovers gracefully.
Two channels
is_error: trueintool_result— structured failure. Claude reads the message and decides what to do.- HTTP / SDK error — request-level failure (timeout, invalid request). You handle these in your code; Claude never sees them unless you surface them.
Error taxonomy
The CCA-F study guide leans heavily on these categories:
Transient errors
Retryable. Network glitches, rate limits, temporary 503.
{
"is_error": true,
"type": "transient",
"retry_after_ms": 2000,
"message": "Rate limited by upstream API. Retry after 2s."
}
Claude can choose to retry or to try a different approach.
Permanent errors
Not retryable. 404, invalid auth, business-logic violation.
{
"is_error": true,
"type": "permanent",
"code": "not_found",
"message": "Ticket TICKET-9999 does not exist."
}
Claude should not retry; it should adapt (ask user, try a different query, escalate).
Validation errors
The model called the tool with bad input. Give Claude what's wrong AND how to fix it.
{
"is_error": true,
"type": "validation",
"message": "Parameter 'order_id' must be a UUID. Got: 'order-42'."
}
Uncertain state
The worst kind. We don't know if the operation succeeded.
{
"is_error": true,
"type": "uncertain",
"message": "Payment was submitted but our confirmation request timed out. Do not retry; verify status with check_payment(payment_id='...') before any next step."
}
This is exam-relevant: the right response is defensive — do not retry the side-effecting call, run a read to disambiguate.
Retry strategy
- Cap retries (3 is typical) at the tool layer, not in the model loop.
- Exponential backoff for transient.
- After cap, return permanent error to the model.
- Idempotent ops can retry safely; non-idempotent ops must not.
Error messages are prompts
Whatever you put in message is read by the LLM. Write it for the LLM:
- Be specific about what failed.
- Suggest what could be tried.
- Avoid jargon from your internal systems.
- Don't include sensitive data (stack traces, tokens, SQL).
Confirmation flows
For destructive tools, the host should ask the user before the call. If declined, return:
{"is_error": true, "type": "user_declined", "message": "User declined this operation."}
Don't loop back into asking.
Graceful degradation
When a tool is unavailable, return a useful fallback:
{
"is_error": true,
"type": "unavailable",
"message": "CRM is down for maintenance until 14:00 UTC. You may proceed with cached customer info if available, or inform the user."
}
Claude can then escalate to the user instead of failing silently.