Skip to content

Make tool.done/tool_result awaitingResponse symmetric with inference.done - #569

Merged
TheGreatAxios merged 1 commit into
mainfrom
cl-5661-make-turn-states-tooldone-handler-symmetric-with
Aug 23, 2026
Merged

Make tool.done/tool_result awaitingResponse symmetric with inference.done#569
TheGreatAxios merged 1 commit into
mainfrom
cl-5661-make-turn-states-tooldone-handler-symmetric-with

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

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

  • stall-watchdog.ts reads awaitingResponse and activeToolCalls together (its own CL-5641 guard: 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).
  • runtime-bridge.ts passes bag.turn.awaitingResponse straight through to the stall-watchdog args and the status chrome; no logic there depends on the old (wrong) timing, so a stricter/later-arriving true value is strictly safer.
  • session-chrome status derivation reads awaitingResponse only alongside streamingType === null, which tool.done already sets — no change in behavior for the single-call case, since activeToolCalls.length is already 0 there.

Tests

Added to src/tui/turn-state.test.ts:

  • two parallel tool calls: first tool.done leaves awaitingResponse false, second sets it true
  • a lone tool.done still sets awaitingResponse true (unchanged behavior)

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. shouldAbortForStall calls awaitingFirstToken first, and every path that sets awaitingResponse: true also sets streamingType: null, so the abort path short-circuits before reaching the activeToolCalls.length === 0 guard — 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.done never satisfied it regardless of the incorrect awaitingResponse.

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_result branch has no test coverage, before or after this PR. Structurally identical to tool.done and traced by hand, so not a defect — just an untested path.

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.
@linear-code

linear-code Bot commented Aug 23, 2026

Copy link
Copy Markdown

CL-5661

@TheGreatAxios
TheGreatAxios merged commit e3a4251 into main Aug 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant