Symptom
The todos panel in the chat UI freezes on a stale snapshot when the browser reconnects in the middle of an active turn (page refresh, WS drop, tab backgrounding for long enough to trigger a reconnect). New TodoWrite calls keep landing in the WS buffer and on the persisted message history, but the panel keeps showing the count from the last TodoWrite that was persisted before the reconnect.
I hit this on a session that ran roughly 10 TodoWrite calls across two turns. The panel showed 4/9 with one task "in progress" even though the latest persisted TodoWrite was 13/13. The mismatch persisted across the rest of the session.
Root cause
handleSessionStatus in web/src/stores/handlers/sessionHandlers.ts rebuilds streamingBlocks from msg.buffered_events via applyStreamEvent, but it never derives currentTodos from those events. The only paths that update currentTodos are:
- The live
handleToolUse handler (works while the WS is live, not during replay).
extractTodosFromMessages(hydrated) on switchSession (only sees persisted DB messages).
So if a TodoWrite arrives in the WS buffer for an in-flight turn that hasn't been persisted yet, the buffer replay populates streamingBlocks correctly but currentTodos is left as whatever the last persisted history had.
Reproduction
- Start a session that issues many
TodoWrite calls in a single turn.
- Refresh the browser tab while the turn is still streaming.
- After reconnect, the todos panel sticks at the last persisted TodoWrite snapshot. Live updates resume eventually but the buffered window between persistence and reconnect is lost from the panel.
Proposed fix
Add an extractTodosFromBuffer(events) helper next to extractTodosFromMessages and call it from handleSessionStatus after rebuilding blocks. Return null when no top-level TodoWrite is in the buffer so the caller preserves the existing value from persisted history. Do not skip the all-completed case (unlike extractTodosFromMessages); the panel's existing 5s auto-hide handles that animation.
Patch coming as a PR linked to this issue.
Symptom
The todos panel in the chat UI freezes on a stale snapshot when the browser reconnects in the middle of an active turn (page refresh, WS drop, tab backgrounding for long enough to trigger a reconnect). New TodoWrite calls keep landing in the WS buffer and on the persisted message history, but the panel keeps showing the count from the last TodoWrite that was persisted before the reconnect.
I hit this on a session that ran roughly 10 TodoWrite calls across two turns. The panel showed
4/9with one task "in progress" even though the latest persisted TodoWrite was13/13. The mismatch persisted across the rest of the session.Root cause
handleSessionStatusinweb/src/stores/handlers/sessionHandlers.tsrebuildsstreamingBlocksfrommsg.buffered_eventsviaapplyStreamEvent, but it never derivescurrentTodosfrom those events. The only paths that updatecurrentTodosare:handleToolUsehandler (works while the WS is live, not during replay).extractTodosFromMessages(hydrated)onswitchSession(only sees persisted DB messages).So if a
TodoWritearrives in the WS buffer for an in-flight turn that hasn't been persisted yet, the buffer replay populatesstreamingBlockscorrectly butcurrentTodosis left as whatever the last persisted history had.Reproduction
TodoWritecalls in a single turn.Proposed fix
Add an
extractTodosFromBuffer(events)helper next toextractTodosFromMessagesand call it fromhandleSessionStatusafter rebuildingblocks. Returnnullwhen no top-levelTodoWriteis in the buffer so the caller preserves the existing value from persisted history. Do not skip the all-completed case (unlikeextractTodosFromMessages); the panel's existing 5s auto-hide handles that animation.Patch coming as a PR linked to this issue.