surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
Documents API error where multiple tool_result blocks are sent with the same tool_use_id when a tool fails. The "Sibling tool call errored" message is incorrectly being sent for the same tool that failed, rather than only for other sibling tools in a parallel batch. Includes: - Root cause analysis - Example JSON from reported incidents - Suggested fix locations and pseudocode - Related request IDs for debugging
| @@ -1,0 +1,123 @@ | ||
| 1 | +# Bug Report: Duplicate tool_result blocks with same tool_use_id | |
| 2 | + | |
| 3 | +## Summary | |
| 4 | +When a tool call fails (particularly observed with the Tmux tool), two `tool_result` blocks are incorrectly sent to the API with the same `tool_use_id`, causing an API error: | |
| 5 | + | |
| 6 | +``` | |
| 7 | +API Error: 400 | |
| 8 | +{"type":"error","error":{"type":"invalid_request_error","message":"messages.X.content.1: each tool_use must have a single result. Found multiple `tool_result` blocks with id: toolu_XXX"}} | |
| 9 | +``` | |
| 10 | + | |
| 11 | +## Root Cause | |
| 12 | +The parallel tool execution error handling code sends a "Sibling tool call errored" message even for the tool that actually failed, when it should only send this message for OTHER sibling tools in the parallel batch. | |
| 13 | + | |
| 14 | +## Reproduction | |
| 15 | +1. Execute a tool call that will fail (e.g., Tmux select-pane on a non-existent window) | |
| 16 | +2. Observe two tool_result messages being generated: | |
| 17 | + - First: The actual error result ("Window not found...") | |
| 18 | + - Second: A "<tool_use_error>Sibling tool call errored</tool_use_error>" message with the SAME tool_use_id | |
| 19 | + | |
| 20 | +## Example JSON (from reported incident) | |
| 21 | +```json | |
| 22 | +// Assistant requests tool use | |
| 23 | +{ | |
| 24 | + "type": "tool_use", | |
| 25 | + "id": "toolu_01K2K2KFvUrwA9PaHGp652zW", | |
| 26 | + "name": "Tmux", | |
| 27 | + "input": {"args": ["select-pane", "-t", "test-perms3:0.0"]} | |
| 28 | +} | |
| 29 | + | |
| 30 | +// First tool_result (correct - actual error) | |
| 31 | +{ | |
| 32 | + "type": "user", | |
| 33 | + "message": { | |
| 34 | + "role": "user", | |
| 35 | + "content": [{ | |
| 36 | + "tool_use_id": "toolu_01K2K2KFvUrwA9PaHGp652zW", | |
| 37 | + "type": "tool_result", | |
| 38 | + "content": "Window not found. The specified window may not exist.", | |
| 39 | + "is_error": true | |
| 40 | + }] | |
| 41 | + } | |
| 42 | +} | |
| 43 | + | |
| 44 | +// Second tool_result (BUG - same tool_use_id!) | |
| 45 | +{ | |
| 46 | + "type": "user", | |
| 47 | + "message": { | |
| 48 | + "role": "user", | |
| 49 | + "content": [{ | |
| 50 | + "type": "tool_result", | |
| 51 | + "content": "<tool_use_error>Sibling tool call errored</tool_use_error>", | |
| 52 | + "is_error": true, | |
| 53 | + "tool_use_id": "toolu_01K2K2KFvUrwA9PaHGp652zW" | |
| 54 | + }] | |
| 55 | + } | |
| 56 | +} | |
| 57 | +``` | |
| 58 | + | |
| 59 | +## Expected Behavior | |
| 60 | +- When a tool fails, only ONE tool_result should be sent for that tool_use_id | |
| 61 | +- The "Sibling tool call errored" message should ONLY be sent for OTHER tool_use_ids in a parallel batch, not for the tool that actually errored | |
| 62 | + | |
| 63 | +## Suggested Fix Location | |
| 64 | +The fix should be in the code that handles parallel tool execution errors: | |
| 65 | + | |
| 66 | +```typescript | |
| 67 | +// Pseudocode for the fix | |
| 68 | +function handleParallelToolResults(toolUseIds: string[], failedToolId: string, error: Error) { | |
| 69 | + const results = []; | |
| 70 | + | |
| 71 | + for (const id of toolUseIds) { | |
| 72 | + if (id === failedToolId) { | |
| 73 | + // Send actual error for the failed tool | |
| 74 | + results.push({ | |
| 75 | + tool_use_id: id, | |
| 76 | + type: "tool_result", | |
| 77 | + content: error.message, | |
| 78 | + is_error: true | |
| 79 | + }); | |
| 80 | + } else { | |
| 81 | + // Only send sibling error for OTHER tools | |
| 82 | + results.push({ | |
| 83 | + tool_use_id: id, | |
| 84 | + type: "tool_result", | |
| 85 | + content: "<tool_use_error>Sibling tool call errored</tool_use_error>", | |
| 86 | + is_error: true | |
| 87 | + }); | |
| 88 | + } | |
| 89 | + } | |
| 90 | + | |
| 91 | + return results; | |
| 92 | +} | |
| 93 | +``` | |
| 94 | + | |
| 95 | +Additionally, a deduplication check before sending to the API would prevent this: | |
| 96 | +```typescript | |
| 97 | +function deduplicateToolResults(messages: Message[]): Message[] { | |
| 98 | + const seenToolUseIds = new Set<string>(); | |
| 99 | + return messages.filter(msg => { | |
| 100 | + if (msg.type === "tool_result") { | |
| 101 | + if (seenToolUseIds.has(msg.tool_use_id)) { | |
| 102 | + return false; // Skip duplicate | |
| 103 | + } | |
| 104 | + seenToolUseIds.add(msg.tool_use_id); | |
| 105 | + } | |
| 106 | + return true; | |
| 107 | + }); | |
| 108 | +} | |
| 109 | +``` | |
| 110 | + | |
| 111 | +## Related CHANGELOG Fixes | |
| 112 | +Similar issues have been fixed before: | |
| 113 | +- v1.0.84: "Fix tool_use/tool_result id mismatch error when network is unstable" | |
| 114 | +- v2.0.0: "Hooks: Reduced PostToolUse 'tool_use' ids were found without 'tool_result' blocks errors" | |
| 115 | +- v2.0.22: "Fix bug causing duplicate permission prompts with parallel tool calls" | |
| 116 | + | |
| 117 | +## Reported By | |
| 118 | +Issue reported via Slack with multiple reproductions showing the Tmux tool triggering this bug. | |
| 119 | + | |
| 120 | +## Request IDs (for debugging) | |
| 121 | +- req_011CVt2KXhCGmrL2YN9sSZQP | |
| 122 | +- req_011CVtFYJLPxn9oB9FEkCnRr | |
| 123 | +- req_011CVtG1PREveTLikakXGEdn |