Drop tool results orphaned by trimMessagesToFitTokenLimit at the removal boundary - #1292
Open
nordicnode wants to merge 2 commits into
Open
Drop tool results orphaned by trimMessagesToFitTokenLimit at the removal boundary#1292nordicnode wants to merge 2 commits into
nordicnode wants to merge 2 commits into
Conversation
The removal run in trimMessagesToFitTokenLimit stops as soon as the token budget is met, which can land between an assistant tool-call and its role:'tool' result. The surviving tool message then reaches the provider without its call and the step fails with 'tool_call_id does not exist' — observed on the find-files request path via getMessagesSubset. After the removal loop, drop results whose call this trim removed; results whose call never existed in the input history pass through unchanged so pre-existing orphans are not silently rewritten, and providerExecuted calls are excluded to mirror the pairing semantics of dropUnansweredToolCalls.
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.
Problem & Context
trimMessagesToFitTokenLimitremoves a contiguous run of oldest messages until the token budget is met — and the run can stop exactly between an assistant message carrying tool-calls and itsrole: 'tool'results. The surviving tool message then reaches the provider without its call and the whole step fails withtool_call_id does not exist(observed on the openai-compatible lane; consumed viagetMessagesSubsetbyfind-files/request-files-prompt.ts:201,271).Reproduction (pre-fix, against the real module):
dropUnansweredToolCallsat the conversion chokepoint intentionally handles only the mirror case (calls whose results are gone) — orphaned results were the gap.Changes Made
packages/agent-runtime/src/util/messages.ts(trimMessagesToFitTokenLimit): after the removal loop, drop any survivingrole: 'tool'message whosetoolCallIdwas present in the input history but is not provided by any surviving assistant message. +38 lines, no other behavior touched.packages/agent-runtime/src/util/__tests__/messages.test.ts: five regression/behavior-preservation tests importing the real production function.Deliberate scoping: only results orphaned by this trim are dropped (their call existed in the input and was removed). Orphans already present in a malformed input history pass through unchanged, as before — this fix does not silently rewrite histories it did not break.
providerExecutedcalls are excluded from both sets, mirroring the pairing semantics ofdropUnansweredToolCalls.Architecture & Conventions Conformance
common/src/types/contracts/, no module monkey patching) — pure function change, logger injected as beforeterminalCommandBroker(no directspawnor TUI-process bypass) — no process execution touchedgetCliEnv()for CLI,getSdkEnv()for SDK, no forbiddengetProcessEnv()imports) — no env access addedIS_FREEBUFFpreserved, no paid features introduced) — product-agnostic bug fiximport typeused for types) — no import changesScope Verification
packages/agent-runtime/web/,freebuff/web/,packages/internal/,packages/billing/,packages/bigquery/, orpackages/build-tools/Testing & Verification
bun run build:sdk(passed cleanly)bun run build:freebuff(passed cleanly)bun cli/scripts/smoke-binary.ts cli/bin/freebuff(passed cleanly — with the CI env var set and a writableHOME; the sandboxed checkout has a read-only home directory)Tests were proven to catch the bug: with the fix stashed, 3 of the new tests fail against the unfixed source (boundary orphan, keepDuringTruncation-interleaved orphan, budget sweep); with the fix applied, all 32 tests in the file pass. Fix and tests were additionally verified by an independent adversarial pass (targeted attacks over duplicate call ids, multi-call assistant messages,
providerExecutedasymmetry, keepDuringTruncation interaction, input-mutation checks, early-return path, plus a seeded property fuzz ≥ 3000 histories): every surviving tool result either has its call, was a pre-existing orphan, or isproviderExecuted-related; input never mutated; no new failures.Verification Output / Log Snippet
New tests (all against the production module, no local reimplementations):
drops a tool result whose call was removed at the boundary— the reported 400 case; also asserts the final'done'assistant message survivesdrops tool results orphaned after a kept keepDuringTruncation message— removal runs are not pure prefixes whenkeepDuringTruncationmessages sit mid-run, so the orphan can appear anywhere, not just leading the kept runkeeps tool results whose call survives the trim(no-trim passthrough)leaves pre-existing orphans in an already-malformed history untouched(scoping guard, under an active trim)keeps the invariant across a sweep of budgets(six budgets × two call pairs, structural assertion)