// the error is the steering
There is no side channel between a failed tool call and the model's next move. The result block you send back is the input to the next decision, and everything the model knows about the failure is the string inside it.
And the budget for getting it right is smaller than most people assume:
Two or three. Each one steered by nothing but the message you wrote. A message that carries no new information produces the same attempt, which means a generic error does not merely fail to help — it burns the recovery attempts you had.
Write it like an instruction
The guidance is unusually blunt, and it picks "failed" as its own anti-example:
"failed", include what went wrong and what Claude should try next (for example, "Rate limit exceeded. Retry after 60 seconds."). This gives Claude the context it needs to recover or adapt without guessing.The test is simple: read your error message and ask whether it describes a different next attempt. If it does not, the retry will be identical.
Note what the good ones share: a fact about the world, and a next move. The last one even redirects to a different tool, which is a move the model cannot invent from a status code.
Flag it, so it is not read as data
Set is_error: true on the result. Without it, an error string is just a string — a tool that returns "ConnectionError: the weather service API is not available (HTTP 500)" as an ordinary result is handing the model something that looks like an answer.
With the flag set, the failure is unambiguous and the model can explain it rather than reason from it — the docs' example response being "I'm sorry, I was unable to retrieve the current weather because the weather service API is not available. Please try again later."
Server tools are not yours to handle. When an Anthropic-hosted tool such as web search fails, "Claude will transparently handle these errors and attempt to provide an alternative response" — you do not return is_error for those.
Delete the class instead of apologising for it
One whole category of failure — malformed inputs, missing parameters, type mismatches — does not need a good error message, because it does not need to happen.
strict: true on your tool definitions. This guarantees that tool inputs will always match your schema exactly, preventing missing parameters and type mismatches.And if the model keeps reaching for the wrong tool rather than calling it wrongly, the error message is the wrong layer entirely — the docs' first suggestion there is "more-detailed description values in your tool definitions". That is a different fix, applied before anything runs.
Two kinds of error, and only one is recoverable
MCP draws the same line from the protocol side, and the distinction is worth carrying into your own handlers.
Hence the spec's asymmetric advice: clients SHOULD pass execution errors to the model to enable self-correction, but MAY pass protocol errors, "though these are less likely to result in successful recovery".
The practical version: if the failure is something a different set of arguments would fix, say what they are. If it is not — the tool does not exist, your server is down — no wording will make the retry work, and the honest move is to stop the loop rather than spend its attempts.
Related: idempotent tools covers what a retry does to your data, which is the other half of this same moment; nothing is telling your agent to stop covers the loop those attempts run inside.
Sources: Claude Platform docs — Handle tool calls; Model Context Protocol specification (draft), Tools → Error Handling. Verified 2026-09-13.
One concept a week. Free.
The deeper, copy-paste version of each ToolCall short — in your inbox.
// total: 0.00 · spam: void · unsubscribe: one click
