Skip to content

fix: auto-extend compression ranges to prevent splitting tool_use/tool_result pairs (Issue #247) - #248

Merged
ranxianglei merged 3 commits into
masterfrom
2026-07-30_tool-pair-integrity
Jul 30, 2026
Merged

fix: auto-extend compression ranges to prevent splitting tool_use/tool_result pairs (Issue #247)#248
ranxianglei merged 3 commits into
masterfrom
2026-07-30_tool-pair-integrity

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #247 — compression ranges can split tool_use/tool_result pairs, creating orphaned references that cause provider API rejection.

Problem: When a compression range includes a tool_use (assistant) but not its tool_result (user), or vice versa, the pruned output has an orphaned tool_use_id/tool_result_id reference → provider API rejects: "tool_result block does not have a corresponding tool_use".

Fix: adjustBoundariesForToolPairs in lib/compress/search.ts auto-extends range boundaries to include paired messages:

  • Collects all non-compress tool callIDs in the range
  • Forward scan (≤20 msgs): extends endIdx to include tool_results matching in-range callIDs
  • Backward scan (≤20 msgs): extends startIdx to include tool_uses matching in-range callIDs
  • Stops at first gap after finding a match

Oracle Review Fixes (commit 2)

Oracle review found tier misclassification risk: block anchors are compress tool_use messages (have callIDs), so forward scan would flip endReference.kind from "compressed-block" to "message", corrupting tier detection in applyCompressionState (T2 → T1).

Two fixes:

  1. Exclude compress tool from scan: compress is always force-protected, its pairs survive intact regardless — no need to adjust for them
  2. Only extend MESSAGE boundaries: block boundaries (bN) are never adjusted, preserving tier metadata

Test Coverage (11 tests)

Test Covers
forward extension tool_use at endIdx, result after
backward extension result at startIdx, use before
pair inside range no extension
no tool calls no extension
multiple results one use, multiple result msgs
parallel calls one message, multiple callIDs
gap tolerance non-tool msg between use and result
flows to resolveSelection toolIds include paired messages
kind change (message→message) backward extension on message boundary
block boundary kind preserved T2 distillation b1→b1, compress excluded
compress excluded from scan message boundary, compress calls don't trigger extension

CI

All 5 checks pass: pr-validation ✓, test (22) ✓, test (24) ✓, build ✓, e2e ✓
949 tests (0 failures).

…ry kind

Oracle review found tier misclassification risk: block anchors are
compress tool_use messages (have callIDs), so forward scan would flip
endReference.kind from 'compressed-block' to 'message', corrupting
tier detection (T2 → T1).

Two fixes:
1. Exclude 'compress' tool callIDs from the scan (compress is always
   force-protected, pairs survive intact regardless)
2. Only extend MESSAGE boundaries, not block boundaries (defense in depth)

Added 2 tests:
- block boundary: kind preserved (b1→b1 T2 distillation)
- compress tool excluded from scan (message boundary with compress calls)
@ranxianglei
ranxianglei merged commit 00cc3f7 into master Jul 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Compression range can split tool_use/tool_result pairs → orphaned tool_result → API rejection

1 participant