fix(daemon): auto-recover transcript on ring_evicted resync#4702
Conversation
When a WebUI client reconnects with a stale SSE cursor past the EventBus ring buffer, the daemon emits state_resync_required with reason ring_evicted. Previously the provider did nothing for this reason, leaving the transcript frozen because the reducer's awaitingResync latch silently dropped all subsequent replay events. Hoist store.reset() before the reason-specific branches so it runs for all resync reasons including ring_evicted. This clears the awaitingResync latch and empties the stale transcript, allowing the ring replay events (already queued by EventBus.subscribe) to flow through the reducer and rebuild the transcript from the surviving ring contents. Also remove the misleading "Reload the session to recover." suffix from the resync error message — reloading re-triggers ring eviction for long sessions, and the WebUI now auto-recovers.
There was a problem hiding this comment.
Pull request overview
This PR fixes a WebUI transcript recovery bug when reconnecting with a stale SSE cursor that has fallen behind the daemon’s EventBus ring buffer (state_resync_required with reason ring_evicted). By resetting the UI store for all resync reasons (including ring_evicted), it clears the awaitingResync latch and stale transcript state so subsequent replay events can rebuild the transcript from the remaining ring contents. It also updates the transcript resync error text to remove the misleading “Reload the session to recover.” guidance now that the WebUI auto-recovers.
Changes:
- Reset the session UI store on all
state_resync_requiredreasons (includingring_evicted) to allow replay events to repopulate transcript state. - Update WebUI provider tests to assert replay rebuild behavior and ensure the resync error block is cleared after reset.
- Simplify the transcript resync error message by removing the “Reload the session to recover.” suffix.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/webui/src/daemon/session/DaemonSessionProvider.tsx | Hoists store.reset() so ring_evicted resync also clears awaitingResync and stale transcript state before replay continues. |
| packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx | Updates/strengthens tests to validate replay events rebuild transcript after ring-evicted resync and that the resync error block is cleared. |
| packages/sdk-typescript/src/daemon/ui/transcript.ts | Removes misleading reload instruction from the State resync required error text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📋 Review SummaryThis PR fixes a bug where the daemon UI transcript becomes permanently corrupted when a WebUI client reconnects with a stale SSE cursor past the EventBus ring buffer. The fix ensures 🔍 General Feedback
🎯 Specific Feedback🔵 Low
✅ Highlights
|
chiga0
left a comment
There was a problem hiding this comment.
Code Review Overview (AI Generated)
PR: #4702 fix(daemon): auto-recover transcript on ring_evicted resync
Type: Bug Fix
Change size: +29/-7 across 3 files
Multi-Round Review (Rounds 0-6): Clean — 0 findings
Round 0 (Design): Correct minimal fix — hoists store.reset() before reason-specific branches so it runs for ALL resync reasons including ring_evicted. This clears the awaitingResync latch and stale transcript, allowing ring replay events to rebuild.
Round 1 (Architecture):
- Single code reordering in
DaemonSessionProvider.tsx—store.reset()moved from inside theepoch_resetandelsebranches to before the if/else - Error message updated to remove misleading "Reload the session to recover." — correct since reloading re-triggers ring eviction for long sessions
Round 2 (Robustness):
ring_evictedpreviously did nothing → transcript stuck. Now resets and allows replay events to rebuild.epoch_resetbehavior unchanged — still resets + sets lastEventId=0- Unknown reasons still trigger full reload (resyncRequested + store.reset + session=undefined)
Round 3 (CI/Security): N/A — purely UI state management.
Round 4 (Performance): No performance impact — just reorders existing operations.
Round 5 (Bug Fix): Root cause correctly identified: ring_evicted was silently ignored, leaving awaitingResync latch stuck. Test updated to verify replay events rebuild transcript after reset.
Round 6 (Undirected): The change is semantically correct — resetting for ring_evicted is the right behavior since the transcript was stale anyway. Edge case (no ring replay events) results in empty transcript, which is still better than being stuck.
LGTM!
This review was generated by QoderWork AI
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — qwen3.7-max via Qwen Code /review
… prompt pipeline (QwenLM#4702)
Summary
state_resync_required(ring_evicted). Previously the provider did nothing for this reason — the reducer'sawaitingResynclatch silently dropped all subsequent ring replay events, leaving the transcript with a permanent history gap.store.reset()before the reason-specific branches so it runs for all resync reasons includingring_evicted. This clears the latch and empties the stale transcript, allowing ring replay events to rebuild it from the surviving ring contents (up to 8000 events).Test plan
npx vitest run test/unit/daemonUi.test.ts— 200 tests passed (no regression on error message assertions)npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx— 37 tests passed🤖 Generated with Qwen Code