Canonicalize tool call identity so it settles activeToolCalls once - #384
Merged
TheGreatAxios merged 1 commit intoAug 8, 2026
Conversation
A streamed inference.tool_call.start/end with no callId registered the call under its name, while the executed tool.start for the same call registered it under a real id. One tool.done only removed the id-keyed entry, so the name-keyed duplicate leaked and pinned activeToolCalls above zero forever, blocking inference.done and connector.reply from ever settling the turn. Goal mode surfaced this worst since its self-continuing governor has no other terminator. Identity is now resolved once at the event boundary: the first id seen for a tool name is recorded, so a later id-bearing announcement for the same call replaces an earlier name-only placeholder in place instead of adding a second entry. The mapping is cleared once its call resolves, so a later call reusing the same tool name in one turn starts clean rather than inheriting a finished call's id.
TheGreatAxios
force-pushed
the
cl-5645-duplicate-call-ids-leak-into-activetoolcalls-pinning-the
branch
from
August 8, 2026 16:47
1ef7f47 to
3532ea2
Compare
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.
Summary
/goal(and other surfaces less often) sat at "working" forever after finishing.src/tui-opentui/turn-state.tstracked the same logical tool call under two different keys — a name-only streamedinference.tool_call.start/.endunder the tool's name, the id-bearingtool.startunder a real id — so onetool.doneonly cleared one entry andactiveToolCallsnever emptied, blocking both turn terminators (inference.done,connector.reply).callIdByNamemapping: a name-only placeholder is promoted in place once a real id is seen, and the mapping is cleared once that call resolves so a later call reusing the same tool name doesn't inherit a stale id.Test plan
src/tui-opentui/turn-state.test.ts: the exact CL-5645 repro (name-only start -> id-bearing tool.start -> one tool.done settles), a same-call start+end idempotency case, concurrent same-named calls resolving independently, and a second same-named call not inheriting a finished call's stale id.bun run typecheck(pre-existing arktypeArkErrorsnarrowing errors unrelated to this change, present throughout the codebase before this branch)bun run buildbun run test(4082 pass, 1 pre-existing unrelated failure:src/agent/lsp-availability.test.ts, an environment-dependent language-server detection check untouched by this diff)Note
Agents on CL-5641/CL-5642 are concurrently editing
turn-state.tsandruntime-bridge.tsin separate worktrees. This diff is isolated but expect possible merge overlap inturn-state.ts.