fix: repair orphaned tool_use/tool_result pairing in message pipeline#21613
fix: repair orphaned tool_use/tool_result pairing in message pipeline#21613delphos-mike wants to merge 1 commit intoanomalyco:devfrom
Conversation
Add repairToolPairing() to ProviderTransform.message() that detects and fixes orphaned tool-call blocks before sending to the API. When a tool-call has no matching tool-result (due to process crash, retry loop leaving orphaned parts, or plugin mutation), the function injects a synthetic tool-result with an interrupted message. This prevents the Anthropic API 400 validation error: 'tool_use ids were found without tool_result blocks immediately after' which permanently poisons sessions because the corrupt history is rebuilt identically on every subsequent turn. Refs: anthropics/claude-code#6836
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
The following comment was made by an LLM, it may be inaccurate: Potential Duplicate Found:
The current PR (21613) focuses on a defensive repair pass in the message pipeline to handle orphaned tool-call blocks, while PR #16751 appears to address similar tool_use/tool_result mismatch issues from a session reconstruction perspective. These might be complementary fixes or potential duplicates depending on their implementation scope. |
|
Closing — live testing proved the orphaned tool_use is introduced by the @ai-sdk/anthropic adapter AFTER the ModelMessage layer where this repair operates. The repair found 0 orphans on the broken session and the error still reproduced with the fix applied. Filed upstream: vercel/ai#14259 |
Summary
repairToolPairing()toProviderTransform.message()that detects orphanedtool-callblocks (those without a matchingtool-result) and injects synthetictool-resultmessages to prevent API rejectiontool_use ids were found without tool_result blocks immediately afterwhich permanently poisons sessionsProblem
When a process crashes during tool execution, tool parts can be left in
pending/runningstate in the SQLite database. WhiletoModelMessages()converts these tooutput-error, there are edge cases where the AI SDK'sconvertToModelMessages()ornormalizeMessages()can produce orphanedtool-callblocks without matchingtool-resultblocks. Once this happens, the session is permanently stuck because:This is a widespread issue — see anthropics/claude-code#6836 (150+ reports).
Solution
A defensive repair pass in
ProviderTransform.message()that runs afternormalizeMessages()and beforeapplyCaching():tool-resultIDs across the message arraytool-callIDs with no matchingtool-resulttoolmessage or injects a new one with a synthetic resultThe repair is O(n), idempotent, and only activates when orphans exist (zero overhead on clean conversations).
Testing
Updated 2 existing tests in
transform.test.tsthat had incomplete tool-call/tool-result pairing (standalone tool-calls without results) to include matching tool messages, making them more realistic. All 121 tests pass.