33d96f19eefeea32ad7f44947f35943d7dd76ff9
A turn could stop producing events and stay `isGenerating` indefinitely. Nothing covered it: the idle timer answers the opposite question — how long a session with NO turn in flight may sit before collection — and every client showed a spinner with no timeout of its own, so a wedged turn presented as a chat that was still thinking. On 2026-08-08 one ran for seventeen minutes inside an auto-compaction, reached over the socket to an iPad, and was indistinguishable there from a dead app. The compaction is silent by design (the PreCompact hook is the only announcement, and the code's own comment allows 2.5 minutes), so there was nothing to distinguish it from. A stall watchdog now rides every emitted event: any sign of life pushes the deadline back, and expiry ends the turn the way a real failure would — isGenerating off, idle re-armed, and an `error` the client can render. The agent process is deliberately left alive, since it may still be working and the next turn resumes it; what this guarantees is that the client is TOLD, which is the part that was missing. The budgets are generous rather than tight — ten minutes of silence normally, twenty while compacting, re-armed from the PreCompact hook because that hook fires as the long silence begins and the deadline the turn is holding was sized for ordinary work. Killing a turn that was about to succeed is worse than the hang this prevents. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
No description provided
42 MiB
Languages
TypeScript
90.9%
Shell
4.7%
JavaScript
4.1%
CSS
0.2%
HTML
0.1%