fix(compression): keep tool_result blocks first when aging annotates a turn - #12920
Merged
diegosouzapw merged 2 commits intoSep 10, 2026
Merged
diegosouzapw merged 2 commits into
diegosouzapw merged 2 commits into
Conversation
…a turn replaceTextContent() prepends the "[COMPRESSED:aging:...]" annotation when a message carries no text block to replace. A user turn that answers a tool_use with a single tool_result block therefore becomes ["text", "tool_result"], and the Anthropic Messages API rejects the whole request: messages.N: `tool_use` ids were found without `tool_result` blocks immediately after Append the annotation instead when the turn contains a tool_result, and keep prepending for every other shape so existing ordering is untouched. Closes diegosouzapw#12890
diegosouzapw
merged commit Sep 10, 2026
e1a1290
into
diegosouzapw:release/v3.8.51
8 of 16 checks passed
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…a turn (diegosouzapw#12920) Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
replaceTextContent()prepends the aging annotation when the message has notext block to replace:
A user turn that answers a
tool_usewith a singletool_resultblock hitsexactly that branch, so aging rewrites it to
["text", "tool_result"]. TheAnthropic Messages API requires the
tool_resultblocks to lead that turn, soupstream rejects the entire request:
This PR appends the annotation when the turn carries a
tool_result, and keepsprepending for every other shape, so no existing ordering changes.
Verifying the report first (#12890)
Confirmed on the tip, at the call site rather than from the description:
progressiveAging.ts::applyAging→setContent→replaceTextContent. Atool-result-only turn has
extractTextContent(content) === "", soreplacedstays false and the prepend branch runs.
applyAging(not by calling the helper directly): a turn atdistanceFromEnd = 5lands in thefullSummarytier and comes back as["text", "tool_result"].The report's suggested one-line fix (append unconditionally) would also move the
annotation for messages that carry no
tool_result— images, for one — so thecondition is scoped to the shape that upstream actually constrains.
Test
tests/unit/compression/aging-tool-result-order-12890.test.tsapplyAging()and assertsthe aged turn's block order is
["tool_result", "text"]and that the trailingblock is the
[COMPRESSED:aging:…]annotation. It goes through the aging entrypoint, not
replaceTextContentin isolation.imageblock still gets["text", "image"].Mutation
!replacedbranch back to the unconditional prepend (the shipped bug)keeps tool_result first through applyAging—got ["text","tool_result"]!replacedbranch to an unconditional append (the report's suggestion)still puts the annotation first when the turn carries no tool_resultEach mutation kills a different test, so neither branch of the new condition is
unpinned.
Regression
The 7 test files that exercise
messageContent.ts/progressiveAging.ts/aggressive.ts—aggressive-fidelity,aggressive,compression-aggressive,golden-eval,progressiveAging,compression-aggressive-spare-last-user, plusthe new file: 54 pass, 0 fail.
Closes #12890