API Features & Capabilities

Tool Result Errors: Returning Failure State to Claude Gracefully

Tools fail — databases time out, records don't exist, inputs don't validate. What you send back determines whether Claude recovers intelligently or hallucinates around the gap.

Claude 3P 101 · Updated July 2026 · Unofficial guide

In a tool-use loop, your application executes functions on Claude's behalf and reports back with tool_result blocks. The happy path is well documented; the unhappy path is where integrations diverge in quality. The Messages API gives you one small, important flag for it: set "is_error": true on the tool_result block, and put an informative message in its content. That single bit tells the model "this call failed" as a first-class fact rather than something to infer from prose.

The mechanics

An error result is structurally identical to a normal one — same tool_use_id linking it back to the model's request, same position inside the next user message — with the flag added:

def dispatch(call):
    try:
        return {"type": "tool_result", "tool_use_id": call.id,
                "content": run_tool(call.name, call.input)}
    except LookupError:
        return {"type": "tool_result", "tool_use_id": call.id,
                "is_error": True,
                "content": ("No customer found for that ID. IDs look "
                            "like 'CUS-' + 8 digits, e.g. CUS-00417293. "
                            "Try search_customers with a name instead.")}
    except TimeoutError:
        return {"type": "tool_result", "tool_use_id": call.id,
                "is_error": True,
                "content": "orders_db timed out after 5s. Safe to retry."}

Note what this is not: an HTTP error. The API call itself succeeds with a 200 — the failure travels inside the conversation as content. Reserve exceptions and status-code handling for genuine API errors (429s, 5xx, invalid requests); tool failures belong in the transcript where the model can reason about them. If Claude requested several tools in parallel, return every result in the same user message, mixing successes and failures as they occurred.

What Claude does with an error result

The model reads the error content like any other observation and decides what to do next: retry with corrected input, switch to a different tool, ask the user for clarification, or explain the limitation in its answer. The quality of that decision tracks the quality of your message almost linearly. "Error" teaches it nothing. "Date must be YYYY-MM-DD; you sent 03/15/2026" usually produces a corrected retry on the very next turn. Anthropic's own server-side tools model the same pattern with machine-readable codes — the code execution tool, for instance, distinguishes execution_time_exceeded from invalid_tool_input from too_many_requests — precisely because different failures warrant different recoveries.

A useful mental split: correctable errors (validation failures, not-found, bad parameters) deserve detail about what valid input looks like, because the model can fix those itself. Environmental errors (timeouts, upstream outages, permission denials) deserve a plain statement of what's unavailable and whether retrying is sensible, so the model routes around them instead of hammering a dead dependency.

Patterns for graceful self-correction

Say what failed, why, and what would succeed. One or two sentences. Include an example of a valid input when the failure was input-shaped.

Don't dump stack traces or secrets. Everything in a tool_result becomes model context: it costs input tokens on every subsequent turn and may surface in output. Log the full trace on your side; send the model a distilled, safe summary — never connection strings, internal hostnames, or credentials.

Cap the retry loop in your code. The model may keep trying variations indefinitely if errors persist. Count failures per tool per conversation and, past a threshold, return a terminal message — "this system is unavailable; do not retry; tell the user" — rather than relying on the model to give up.

Prevent the preventable with strict tool use. Adding "strict": true to a tool definition guarantees tool calls match your schema exactly, eliminating an entire class of malformed-input errors before your handler ever runs. Validation errors that remain are then genuinely semantic ones worth explaining.

Rule of thumb: write error content as if briefing a capable colleague who will immediately act on it — cause, constraint, next step. If your message wouldn't help a human retry correctly, it won't help Claude either.

Platform notes

The is_error convention is part of core tool use, which is generally available on all four third-party platforms — Claude Platform on AWS, Amazon Bedrock, Google Vertex AI, and Microsoft Foundry — so these patterns port unchanged.

Where to go next

Handling several simultaneous calls (and their mixed successes) is covered in Parallel Tool Calls. For API-level failures — the ones that are HTTP errors — see API Error Codes, and for schema guarantees, Strict Tool Use.

Sources