You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three Criticals from R6 review (4351217188) all pointing at real bugs
introduced by R4/R5 work — not false positives. Fixes plus regression
tests.
## Critical 1 — same-session reconnect never clears the latch
When the daemon emitted `state_resync_required`, the reducer set
`awaitingResync = true`. The webui provider dispatched
`assistant.done { reason: 'reconnected' }` after re-attaching SSE but
never called `store.clearAwaitingResync()`. Result: events flowed in
on the fresh stream but every one got dropped by the
`applyDaemonTranscriptEvent` passthrough guard. Transcript appeared
permanently frozen with no diagnostic clue (the `console.warn` fired
on each drop, but the user wouldn't necessarily check DevTools).
Fix: in `DaemonSessionProvider.tsx`, after dispatching the synthetic
`reconnected` `assistant.done`, check `awaitingResync` and clear it
BEFORE the new SSE event loop starts.
## Critical 2 — updateCurrentToolPointer breaks on undefined status
In `upsertToolBlock`, a new tool block is created with
`status: event.status ?? 'pending'`. But `updateCurrentToolPointer`
was called with raw `event.status` — when undefined, the function's
own `if (status === undefined) return;` guard short-circuited without
ever pointing at the new (visually-pending) block.
Result: `selectCurrentTool` returned `undefined` for daemon events
that omitted the explicit `status` field, while the block sat at
"pending" in the UI — invisible to the current-tool selector.
Fix: pass the EFFECTIVE status (`event.status ?? 'pending'`) so the
pointer logic mirrors the actual stored status.
## Critical 3 — clearAwaitingResync flow chicken-and-egg
The earlier (R4) JSDoc documented the recovery flow as: "re-subscribe
with `Last-Event-ID: 0`, then call clearAwaitingResync after replay
drains." But while the latch is true, EVERY non-passthrough event is
dropped at `applyDaemonTranscriptEvent`. So during the replay drain,
zero events made it into state, and clearing the latch afterward did
nothing — transcript permanently empty.
Correct flow: clear FIRST, then stream events. Updated JSDoc on both
`types.ts` interface and `store.ts` impl to document this clearly.
Added a regression test (`clearAwaitingResync AFTER dispatching events:
events ARE dropped`) that pins the correct flow in code.
## Regression tests (+3)
- `undefined status` creates pending block AND sets currentToolCallId
- clear-then-dispatch ✓ events flow
- dispatch-then-clear ✗ events dropped (correct flow documentation)
## Validation
| | |
|---|---|
| SDK tests | **175/175** (was 172, +3) |
| WebUI tests | **9/9** |
| SDK typecheck | clean |
| WebUI typecheck | clean |
## Note on doudouOUC heads-up
QwenLM#4469 (main → daemon_mode_b_main sync, 45 commits since 2026-05-19)
will land soon. doudouOUC's note says rebase should be smooth (no
daemon-ui surface conflicts). Will rebase on the cron's next pass
after QwenLM#4469 merges.
Generated with AI
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
0 commit comments