Make tool.done/tool_result awaitingResponse symmetric with inference.done - #569
Merged
TheGreatAxios merged 1 commit intoAug 23, 2026
Conversation
tool.done and tool_result set awaitingResponse: true unconditionally,
even when other tool calls in a parallel fan-out are still active.
inference.done and connector.reply already gate on activeToolCalls,
so this made the turn-state invariant ("awaitingResponse means the
turn is idle, waiting on the model") inconsistent across settlement
paths. Compute the post-removal activeToolCalls once and use it for
the awaitingResponse decision on both event types.
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.
Closes CL-5661
The asymmetry (confirmed still present on main)
In src/tui/turn-state.ts, inference.done and connector.reply both gate awaitingResponse on activeToolCalls.length > 0 before declaring the turn settled. The tool.done and tool_result handlers set awaitingResponse: true unconditionally, so the first tool.done in a parallel fan-out could claim the turn was idle while a sibling call was still outstanding.
CL-5641 patched the one consumer that noticed (stall-watchdog.ts's own activeToolCalls guard). This change fixes the root cause in turn-state itself so every future consumer inherits a correct signal.
Fix
tool.done and tool_result now compute the post-removal activeToolCalls list once and set awaitingResponse to activeToolCalls.length === 0, matching inference.done / connector.reply.
Consumers traced
awaitingResponse && activeToolCalls.length === 0). With this fix awaitingResponse is never true while activeToolCalls is non-empty, so that guard is now redundant but harmless — left in place as defense-in-depth, per the issue's own suggestion. Not touched (out of scope for this lane).Tests
Added to src/tui/turn-state.test.ts:
bun test src/tui/turn-state.test.ts src/tui/stall-watchdog.test.ts— 67 pass, 0 fail.bunx tsc --noEmit— clean.Review correction
Review traced the watchdog paths and found this PR does not change when the stall watchdog arms, in either direction.
shouldAbortForStallcallsawaitingFirstTokenfirst, and every path that setsawaitingResponse: truealso setsstreamingType: null, so the abort path short-circuits before reaching theactiveToolCalls.length === 0guard — that guard was already dead for aborts, before and after this change. On the notice path the guard was live but already required zero active calls, so a fan-out first-tool.donenever satisfied it regardless of the incorrectawaitingResponse.So the real, verified value here is status/chrome display correctness — during a parallel fan-out the "awaiting response" indicator no longer flashes true while siblings are still running. The original framing as a watchdog-safety fix overclaimed; recording that rather than leaving it in the history.
Also noted: the
tool_resultbranch has no test coverage, before or after this PR. Structurally identical totool.doneand traced by hand, so not a defect — just an untested path.