Best practice L4 · Multi-AI systems informational

Make Tools Fail in a Way Claude Can Recover From

What it is

A design rule for tools Claude calls: when a tool fails, return an error the model can act on — what went wrong, why, and what a valid call would look like — instead of a raw stack trace or a bare status code. The error message is part of the prompt Claude reads next.

Opaque failuredead end· Returns 'Error 500'· Claude can't tell why· Retries the same call, loopsActionable failurepath forward→ Says what was wrong→ Says what to do instead→ Claude corrects and proceeds
An error message is a prompt too — write it for the model that will read it.

Why it works

In an agent loop, a tool result feeds straight back into Claude's next decision. A message like '500 Internal Error' gives it nothing to correct, so it retries blindly or gives up. A message like 'date must be YYYY-MM-DD; you sent 03/14/2025' tells Claude exactly how to fix the call, and it usually does on the next turn. Treating errors as instructions — not just as failures to report — is what turns a brittle tool chain into one that self-corrects.

When to use it

Any function-calling or agent setup where Claude makes multiple tool calls and needs to adapt: API wrappers, database queries, file operations, anything with validation or rate limits. Especially when you've seen the model loop on a failing call.

When not to use it

One-shot tools that either succeed or hand control straight back to a human — if there's no next model turn to consume the error, rich phrasing for the model is wasted effort. Still log the detail for yourself.

Prompt

Design the error responses for this tool before the happy path. For each failure mode (bad input, not found, rate limited, unauthorised), return a short message that states: (1) what was wrong, (2) why, (3) a concrete example of a valid call. Never return a raw stack trace to the model. Here is the tool: <spec>.

Example

A search_orders tool used to return {"error": true} on a malformed filter, and Claude would retry the identical call in a loop. Rewritten, it returns "status must be one of: open, shipped, cancelled — you sent 'complete'". On the next turn Claude sends status: shipped and the loop resolves in one step instead of five.

Advanced version

Distinguish retryable from terminal failures explicitly — mark rate limits and transient network errors as 'retry after N seconds' and mark bad-input or not-found as 'do not retry, change the call.' Claude can then back off on the former and correct on the latter, instead of treating every failure the same way.

Common mistakes

  • Leaking raw exceptions or full stack traces into the tool result — noise the model can't use and a potential info-leak.
  • Returning success-shaped output on partial failure, so Claude proceeds on data that isn't actually there.
  • Making every error look identical, so the model can't tell 'retry' from 'fix your input' from 'stop'.

Related