Skip to content

fix: repair orphaned tool_use/tool_result pairing in message pipeline#21613

Closed
delphos-mike wants to merge 1 commit intoanomalyco:devfrom
delphos-mike:fix/repair-orphaned-tool-pairing
Closed

fix: repair orphaned tool_use/tool_result pairing in message pipeline#21613
delphos-mike wants to merge 1 commit intoanomalyco:devfrom
delphos-mike:fix/repair-orphaned-tool-pairing

Conversation

@delphos-mike
Copy link
Copy Markdown

Summary

  • Adds repairToolPairing() to ProviderTransform.message() that detects orphaned tool-call blocks (those without a matching tool-result) and injects synthetic tool-result messages to prevent API rejection
  • Fixes the Anthropic API 400 validation error tool_use ids were found without tool_result blocks immediately after which permanently poisons sessions

Problem

When a process crashes during tool execution, tool parts can be left in pending/running state in the SQLite database. While toModelMessages() converts these to output-error, there are edge cases where the AI SDK's convertToModelMessages() or normalizeMessages() can produce orphaned tool-call blocks without matching tool-result blocks. Once this happens, the session is permanently stuck because:

  1. Every turn rebuilds the same broken message sequence from the DB
  2. The API rejects with a 400 (not retryable)
  3. The error is set on the NEW assistant message, not the historical one with the broken tool pairing
  4. The historical message has no error flag, so it's never skipped

This is a widespread issue — see anthropics/claude-code#6836 (150+ reports).

Solution

A defensive repair pass in ProviderTransform.message() that runs after normalizeMessages() and before applyCaching():

  1. Collects all tool-result IDs across the message array
  2. Finds tool-call IDs with no matching tool-result
  3. For orphans, either appends to an adjacent tool message or injects a new one with a synthetic result

The repair is O(n), idempotent, and only activates when orphans exist (zero overhead on clean conversations).

Testing

Updated 2 existing tests in transform.test.ts that 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.

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
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Apr 9, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

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.

@delphos-mike
Copy link
Copy Markdown
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours. needs:issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant