C.W.K.
Stream
Lesson 03 of 06 · published

Tool Errors, Retries, and Telling the Model About Them

~14 min · errors, retries, tool-result

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

Errors are tool_result content

When a tool fails, do not raise the exception out of the loop — return a tool_result with an error payload. Claude can read 'database query failed: timeout' and decide to retry, ask the user, or pivot. Hiding errors makes the model blind.

The is_error flag

Tool result blocks support is_error: True. Use it to flag programmatic errors so the model knows the difference between a successful query that returned 'no rows' and a query that failed. Both have content; only one is an error.

Retry budgets per tool

Some tools (HTTP fetches, database queries) deserve a retry on transient errors before reporting back to the model. Others (writes with side effects) should fail fast. Bake the policy into the handler, not the loop — the loop should be tool-agnostic.

Principle: Errors are part of the tool's contract. Surface them; the model is a better recovery agent than your code paths.

Code

Returning errors as tool_result content·python
import json

def invoke_tool(name: str, arguments: dict, tool_use_id: str) -> dict:
    handler = HANDLERS.get(name)
    if not handler:
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "is_error": True,
            "content": f"unknown tool: {name}",
        }
    try:
        out = handler(**arguments)
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "content": json.dumps(out),
        }
    except TransientError as e:
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "is_error": True,
            "content": f"transient: {e}; safe to retry",
        }
    except Exception as e:
        return {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "is_error": True,
            "content": f"permanent: {e}; consider asking the user for clarification",
        }
Per-tool retry policy in the handler·python
import time

def http_fetch(url: str, attempts: int = 3) -> dict:
    last = None
    for i in range(attempts):
        try:
            r = httpx.get(url, timeout=10.0)
            r.raise_for_status()
            return r.json()
        except (httpx.TimeoutException, httpx.HTTPStatusError) as e:
            last = e
            time.sleep(2 ** i)
    raise TransientError(f"http_fetch failed after {attempts} attempts: {last}")

External links

Exercise

Wrap one of your tool handlers in the error pattern. Force a transient failure and watch how the model handles it. Force a permanent failure and watch the difference.
Hint
The model often retries on transient errors and asks the user on permanent ones. The signal you give it shapes the recovery.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.