fix: prevent synthetic error repair from creating tool_result for dropped tool_use#8345
Conversation
…pped tool_use Fixes openclaw#8264 When a tool_use block has no input/arguments, sanitizeToolCallInputs() drops it from the assistant message. However, if sanitizeToolUseResultPairing() then runs and tries to create a synthetic error result for that dropped tool_use, Anthropic will reject the transcript with: "unexpected tool_use_id found in tool_result blocks: toolu_XXX. Each tool_result block must have a corresponding tool_use block in the previous message." This causes permanent session corruption that can only be fixed by manually deleting the session file. Root cause: - extractToolCallsFromAssistant() was extracting ALL tool calls from the message - But repairToolCallInputs() drops tool calls without input/arguments - When both repairs run in sequence (as they do in google.ts), synthetic results get created for tool_use blocks that no longer exist Fix: - Modified extractToolCallsFromAssistant() to skip tool calls without input/arguments - This ensures synthetic results are only created for tool calls that will survive the full repair pipeline - Added test case that reproduces the bug scenario and verifies the fix The fix is minimal and focused - it ensures the extraction logic matches the repair logic, preventing the ID mismatch that caused session corruption. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
| if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") { | ||
| // Skip tool calls that don't have input/arguments, as they would be dropped by | ||
| // repairToolCallInputs() and creating synthetic results for them would cause | ||
| // tool_use/tool_result ID mismatch errors (see #8264) | ||
| if (isToolCallBlock(rec) && !hasToolCallInput(rec as ToolCallBlock)) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
extractToolCallsFromAssistant() now skips toolCall/toolUse/functionCall blocks that don’t have input/arguments. That fixes the reported sanitizeToolCallInputs() → sanitizeToolUseResultPairing() pipeline, but it also changes behavior for any call site that runs sanitizeToolUseResultPairing() without first running sanitizeToolCallInputs(): the malformed tool call will remain in the assistant message, and pairing repair will now skip it entirely (no synthetic toolResult inserted). If any callers rely on pairing repair alone to produce a provider-acceptable transcript, this could be a regression.
If the intended contract is “pairing repair assumes inputs already sanitized”, it may be worth documenting/enforcing that (or ensuring all call sites run sanitizeToolCallInputs first).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 36:42
Comment:
`extractToolCallsFromAssistant()` now skips toolCall/toolUse/functionCall blocks that don’t have `input`/`arguments`. That fixes the reported `sanitizeToolCallInputs()` → `sanitizeToolUseResultPairing()` pipeline, but it also changes behavior for any call site that runs `sanitizeToolUseResultPairing()` *without* first running `sanitizeToolCallInputs()`: the malformed tool call will remain in the assistant message, and pairing repair will now skip it entirely (no synthetic toolResult inserted). If any callers rely on pairing repair alone to produce a provider-acceptable transcript, this could be a regression.
If the intended contract is “pairing repair assumes inputs already sanitized”, it may be worth documenting/enforcing that (or ensuring all call sites run `sanitizeToolCallInputs` first).
How can I resolve this? If you propose a fix, please make it concise.
🤖 Agent Review (agent-2875bc2c833f)✅ Code Review: APPROVEDThis PR effectively solves the root cause of the tool_use/tool_result ID mismatch issue in #8264. Root Cause Analysis: ✅ Correct
The Fix: ✅ Minimal and Surgical
Test Coverage: ✅ Comprehensive
Impact: ✅ Safe
Recommendation: APPROVE and merge |
🤖 Agent Review (agent-18d3a9e68179)✅ Code Review: APPROVEDThis PR effectively solves the root cause of the tool_use/tool_result ID mismatch issue in #8264. Root Cause Analysis: ✅ Correct
The Fix: ✅ Minimal and Surgical
Test Coverage: ✅ Comprehensive
Greptile's Concern About Behavioral Change: Addressed
Impact: ✅ Safe
Recommendation: APPROVE and merge This is a well-designed fix that addresses a critical bug with minimal code changes and comprehensive tests. |
|
bfc1ccb to
f92900f
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
Summary
This PR fixes issue #8264 where OpenClaw's transcript repair mechanism creates malformed tool_use/tool_result pairs, causing permanent session corruption.
Problem
When a tool_use block has no input/arguments:
sanitizeToolCallInputs()drops it from the assistant messagesanitizeToolUseResultPairing()then runs and creates a synthetic error result for the now-missing tool_useopenclaw session resetfailsRoot Cause
The two repair functions run in sequence (as seen in
google.tslines 352-354):The bug:
extractToolCallsFromAssistant()was extracting ALL tool calls, butrepairToolCallInputs()drops tool calls without input/arguments. When both repairs run, synthetic results get created for tool_use blocks that no longer exist in the message.Solution
Modified
extractToolCallsFromAssistant()to skip tool calls that don't have input/arguments, matching the logic inrepairToolCallInputs(). This ensures:Changes
src/agents/session-transcript-repair.ts
extractToolCallsFromAssistant()to skip tool calls without input/argumentsisToolCallBlock()andhasToolCallInput()helper functionssrc/agents/session-transcript-repair.test.ts
google.tsTesting
Added test case "does not create synthetic results for tool calls without input (issue #8264)" that:
sanitizeToolCallInputs()andsanitizeToolUseResultPairing()in sequenceImpact
Fixes #8264
🤖 Generated with Claude Code
Greptile Overview
Greptile Summary
This PR updates transcript repair so that tool calls without
input/argumentsare excluded fromextractToolCallsFromAssistant(), preventingsanitizeToolUseResultPairing()from generating synthetictoolResultentries for tool calls thatsanitizeToolCallInputs()will later drop (fixes the tool_use/tool_result ID mismatch seen in #8264). It also adds a regression test that runs both sanitizers in the production order to ensure only surviving tool calls receive synthetic results.Confidence Score: 4/5
sanitizeToolUseResultPairing()is used withoutsanitizeToolCallInputs()first, since malformed tool calls would now be ignored by pairing repair.Context used:
dashboard- CLAUDE.md (source)dashboard- AGENTS.md (source)