Partial Failure: Where a Tool-Call Loop Breaks First

When a chatbot or callbot calls several tools in one turn — search, booking, payment — the Claude API expects exactly one tool_result block for every tool_use block. Anthropic's official guide requires the tool_result block to appear in the very next message after its matching tool_use block, and inside that user message, every tool_result must come before any text. A previous post covered turn detection and barge-in cancellation — a design for reacting to user interrupts. This one covers a different failure surface: what happens when the tool calls themselves partially fail. Break that ordering and the API returns "tool_use ids were found without tool_result blocks immediately after"; drop just the failed block out of three and the two successful results get rejected along with it.

Not All Failures Are Equal: Execution Errors vs. Call Errors

Anthropic's docs split tool failures into two categories. An execution error is the tool itself dying — a payment API returning a 500. A call error is the request never being well-formed in the first place, like Claude omitting a required booking date. For call errors, returning is_error: true with a note on what's missing lets Claude self-correct — it retries 2-3 times before giving up and apologizing to the user. Let a human step in before that self-correction window plays out, and you burn a retry budget that would have resolved itself. That's the argument for branching execution errors and call errors into separate code paths from the start.

From Design to Operations: A Partial-Failure Recovery Guide

Set target numbers along three axes. Aim for under 500ms from partial-failure detection to a user-facing notice, a self-correction success rate of 70%+ for call errors (resolved within 3 retries), and a hard rule that any tool failing more than 3 times in one session escalates to a human 100% of the time. Repetition-cap design for tool-call loops stops runaway looping outright; this cap is a separate mechanism that catches a different case — repetition that stays within normal bounds but keeps failing at the same point.

Recurring failures fall into two broad patterns. First, returning an execution error as a bare "failed" gives Claude nothing to work with, so it calls the same tool with the same input and hits the same error again. Anthropic's guide recommends messages like "Rate limit exceeded. Retry after 60 seconds." — pairing the cause with the next action — and that format change alone measurably shifts behavior from pointless re-calls toward wait-then-retry. Second, when one tool in a parallel batch fails and you fall back to sequential execution, it's easy to simply omit the tool_result for the remaining tool_use blocks that never ran.

The fix for that second pattern is filling in a placeholder result for calls you chose not to execute. Anthropic's docs are explicit: if an earlier call failed and you're skipping the ones after it, you still return a tool_result for that tool_use_id — is_error: true, with a note such as "Not executed: a preceding call failed." Since parallel execution carries no ordering guarantee, match results by tool_use_id rather than array position, or a failed call's error message can end up attached to the slot of a call that actually succeeded.

Pre-launch scenario tests should force three cases without exception: a forced 500, a missing required parameter, and a timeout on exactly one call inside a parallel batch. Logs need tool_use_id, error class (execution vs. call), retry count, and how it ultimately resolved (model self-correction or human) as fields — without them you can't trace, session by session, which tool is the actual source of repeated failures. Before surfacing any error message to a user, run it through a masking pass to keep payment details or contact info from leaking through.

Weekly, tally which tools hit the 3-retry cap and how the resolution split between self-correction and human handoff, then compare error-message phrasing to see which formats actually move the retry success rate. Log every message-format change separately from code commits, so when next week's success rate moves, you can trace the cause down to the specific wording change.

Checklist to Use Right Away

Partial failure in a multi-tool-call loop isn't one error — it's a protocol problem with three distinct branches: execution errors, call errors, and un-executed placeholders. Match results by tool_use_id, let the model's own 2-3 self-correction attempts run first on call errors, and pack execution-error messages with cause and next action. Do that and a session stops silently looping on retries that never had a path to succeed.

References

Handle tool calls — Claude Docs

Parallel tool use — Claude Docs