← Back to PRs

#16095: fix: remove orphaned tool_result blocks during compaction (#15691)

by claw-sylphx open 2026-02-14 08:23 View on GitHub →
agents stale size: S
## Summary Fixes #15691 After a request is interrupted mid-tool-execution, synthetic `tool_result` blocks are inserted for transcript repair. When context compaction later triggers, the compactor removes assistant messages containing `tool_use` blocks but leaves the corresponding `tool_result` blocks behind. The Anthropic API then rejects all subsequent requests with: > "unexpected tool_use_id found in tool_result blocks" ## Changes ### `src/agents/pi-embedded-runner/compact.ts` - Added post-compaction `repairToolUseResultPairing` call immediately after `session.compact()` returns - When orphaned `tool_result` blocks are detected and removed, logs the cleanup with session context - Only runs when `transcriptPolicy.repairToolUseResultPairing` is enabled (consistent with pre-compaction behavior) - Runs before token estimation so post-compaction token counts reflect the repaired state ### `src/agents/session-transcript-repair.e2e.test.ts` - Added test suite for post-compaction orphaned tool_result scenarios: - Orphaned tool_results after compaction removes tool_use messages - Synthetic tool_results from interrupted requests surviving compaction (exact #15691 scenario) - Mixed valid pairs + orphans (only orphans removed, valid pairs preserved) - No-op when all tool_use/tool_result pairs are valid ## Root cause The existing `sanitizeToolUseResultPairing` was only called *before* compaction (after `limitHistoryTurns`). After `session.compact()`, the SDK replaces older messages with a summary but can leave orphaned `tool_result` blocks that no longer have matching `tool_use` blocks. The fix re-runs the same repair logic after compaction completes. <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes a bug where orphaned `tool_result` blocks survive context compaction and cause Anthropic API 400 errors (`"unexpected tool_use_id found in tool_result blocks"`). The root cause: after `session.compact()` replaces older messages with a summary, `tool_result` blocks whose matching `tool_use` was in a compacted assistant message become orphaned. The fix re-runs the existing `repairToolUseResultPairing` function immediately after compaction completes, gated behind the same `transcriptPolicy.repairToolUseResultPairing` flag used pre-compaction. - Adds post-compaction `repairToolUseResultPairing` call in `compact.ts` with appropriate logging and early-exit when no repairs are needed - Runs before token estimation so post-compaction counts reflect the repaired transcript state - Adds four well-structured test cases covering the primary failure scenario, synthetic tool results from interrupted requests, mixed valid/orphaned pairs, and a no-op baseline <h3>Confidence Score: 5/5</h3> - This PR is safe to merge — it applies an existing, well-tested repair function at an additional call site with no behavioral changes to the repair logic itself. - The change is minimal and well-scoped: it reuses the existing `repairToolUseResultPairing` function (no new logic), is gated behind the same policy flag, only modifies messages when repairs are actually needed, and includes comprehensive test coverage for all relevant scenarios. The single call site for `session.compact()` has been verified. - No files require special attention. <sub>Last reviewed commit: 0f88b2e</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs