fix(loop): make text-mode tool_call ids unique across the history - #40
Merged
Merged
Conversation
Text-mode parsers number calls per response (qwen_tc_0, fc_tc_0, ...), so
every turn reused the same tool_call_id, and the engine's call_{turn}_{n}
fallback collided again after resuming via initial_messages. Assign a
turn-scoped id with a random suffix to every non-native call right after
parsing, before filtering, so observers, blocked/dropped replies and executed
results share the final id. Native provider ids are kept verbatim.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 25, 2026
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
Text-mode tool-call parsers number calls per response (
qwen_tc_0,seed_tc_0,fc_tc_0,mcp_tc_0,dangling_tc_0), so the sametool_call_idrepeated every turn. The engine's own fallbackcall_{turn}_{n}also collided after a resume throughinitial_messages, becauseturnrestarts at 1. Anything keyed bytool_call_idcould then resolve to the wrong turn's result, for example compaction's preserved tool results and spill recovery handles.Ordering within a parallel batch was already correct. The fix only affects id uniqueness across turns.
Fix
_assign_unique_tool_call_idsruns right after parsing. It rewrites every non-native call id tocall_{turn}_{idx}_{8 hex}. Native provider ids (the ones present in the assistant message'stool_calls) are kept verbatim, because the tool reply must echo them. Since this runs before landing/cap filtering, observers, blocked or dropped replies, and executed results all see the same final id.Parsers and
execute_toolsare unchanged.Tests
test_text_parsed_call_ids_stay_unique_across_turns_and_resume: 2 turns × 2 calls, run once and again as a resume, gives 8 unique ids with bodies in call order. It fails onmainwithcall_1_0duplicated.test_native_tool_call_ids_are_kept_verbatimtests/core, the parser tests, the trajectory observer tests and the facade tests pass against this branch viaPYTHONPATH.Release note:
changes/40.fix.md.🤖 Generated with Claude Code
Release workflow
Uses the shared centralized-release workflow in #44. This PR keeps the current package version and contributes its own independent
changes/fragment. Version, lockfile and changelog aggregation happen in a later release PR. #40, #42 and #43 were tested in all six merge orders without conflicts.