Claude Academy
Sign in

Vault / wiki/201/error-handling-in-tools.md

updated 2026-05-28

Error 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

  1. is_error: true in tool_result — structured failure. Claude reads the message and decides what to do.
  2. 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.

See also