fix: typing while a tool runs re-armed the stall watchdog and reset the turn - #298
Merged
Merged
Conversation
…he turn
Reported symptom: send a message while Claude is working and the session goes
quiet, then needs a "please continue" to come back.
A mid-turn push carved out `waiting_approval` when re-asserting status, but not
`tool_running`:
if (this.#status !== "waiting_approval") this.#setStatus("thinking");
Both are real states the push does not end, and both are load-bearing twice
over. Frontends key off them — the approval bar off waiting_approval, the
running-tool card off tool_running — so the flip made the session header
disagree with a tool card still showing "executing". Worse, `#watchdogPaused()`
keys off them: a genuinely-executing tool is a LEGITIMATELY silent run, which is
exactly why the stall watchdog pauses there.
So typing during a long tool silently re-armed the watchdog against it. Once the
stall window lapsed the run was force-recovered:
⚠️ Turn timed out — no activity for 0s. The session was reset;
send your message again to continue.
killing a healthy tool AND the message the user had just queued into it. The
longer the tool (a warehouse sweep, a big build), the more certain the kill —
and typing while it ran is what shortened its leash.
Keep tool_running for the same reason waiting_approval was already kept.
Anything else still becomes "thinking": the push does start model work there.
When the tool finishes, the existing tool-completion path moves it to thinking
and the watchdog re-arms normally.
Two tests, both verified to fail without the fix: the status is preserved
across a mid-turn push during a running tool, and — the damage that actually
mattered — sitting past the stall window with the tool still running produces no
timeout message, no provider teardown, and no second turn.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
abhijitjavelin
approved these changes
Aug 19, 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.
Diagnoses the reported symptom: send a message while Claude is working and the session goes quiet, then needs a "please continue" to come back.
Root cause
The mid-turn push path carved out
waiting_approvalwhen re-asserting status, but nottool_running:Both are real states the push does not end, and both are load-bearing twice over:
waiting_approval, the running-tool card offtool_running. The flip made the session header say "thinking" while the tool card still said "executing", which is exactly what the reported transcript shows.#watchdogPaused()keys off them. A genuinely-executing tool is a legitimately silent run — that is precisely why the stall watchdog pauses there.So typing during a long tool silently re-armed the watchdog against it. Once the stall window lapsed, the run was force-recovered:
killing a healthy tool and the message the user had just queued into it. The longer the tool — a warehouse sweep, a big build — the more certain the kill. Typing while it ran is what shortened its leash.
Fix
Keep
tool_runningfor the same reasonwaiting_approvalwas already kept. Anything else still becomesthinking— the push does start model work there. When the tool finishes, the existing tool-completion path moves it tothinkingand the watchdog re-arms normally.Tests
Two, both verified to fail without the fix (I reverted the one-line change and re-ran to confirm they aren't vacuous):
⚠️ Turn timed outmessage above.The existing
waiting_approvalmid-turn test and the "long tool on a backend without mid-turn injection" test both still pass, so this closes the gap between them rather than widening either.Full suite: 2313 pass, 0 fail; lint + typecheck clean.
Scope note
This fixes one confirmed mechanism with this symptom. If quiet sessions persist after it lands, the next thing I'd look at is
#pendingMidTurnCount: it is incremented per querying push and only decremented on aturn_done, so if a push ever fails to produce the extra turn boundary it expects, the turn's real terminalturn_donegets absorbed as an intermediate one and the consumer waits forever. That path is currently backstopped only by the stall watchdog — the same watchdog this PR stops mis-firing. Worth a look if the symptom recurs, but I did not want to speculatively change reconciliation logic in the same PR as a proven one-line state fix.