Skip to content

fix(desktop): paint backend-chained turns that start after Stop - #124914

Open
Finn763 wants to merge 1 commit into
NousResearch:mainfrom
Finn763:fix/122723-swallowed-chained-turns
Open

Finn763 wants to merge 1 commit into
NousResearch:mainfrom
Finn763:fix/122723-swallowed-chained-turns

Conversation

@Finn763

@Finn763 Finn763 commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Mechanism

The desktop renderer kept a session-wide interrupted boolean set by Stop (cancelRun) and cleared only by the user-submit path (submit.ts seedOptimistic). Backend-chained turns (a /goal continuation, a completion drain flushing queued background-process notifications) never pass through submit, so their message.start hit the early-return guard in gateway-event/message-stream.ts and the whole turn was silently dropped: no bubble armed, and the later message.complete settled flags in completeAssistantMessage without appending the persisted reply. The transcript ended abruptly while state.db held the reply.

Fix: treat message.start as the backend accepting a NEW turn, so it clears the Stop latch and arms normally instead of early-returning. One guard site changed; the sibling guards (mutateStream, finalizeInterimAssistantMessage, completeAssistantMessage interrupted branch, session-info busy-rearm) are untouched and still drop stale events of the CANCELLED turn while the latch is set. This mirrors the existing submit-path contract (fresh turn clears the latch) at the only boundary every backend turn crosses.

RED-to-GREEN evidence

New spec apps/desktop/src/app/session/hooks/use-message-stream/interrupted-latch.test.tsx (the issue's inlined repro, verbatim): mounts the real useMessageStream hook and drives message.start/delta/complete through the real handlers.

  • Before fix: 1 failed | 1 passed — chained-turn reply missing from state.messages (AssertionError: expected false to be true at the chained-reply assertion).
  • After fix: 2 passed (2).

Neighbor test results

Full use-message-stream hook suite: 48 files, 233 tests, all pass — including interrupted-reply.test.tsx (Stop still seals the live bubble, late deltas still dropped, status: interrupted completions still extend-only) and clarify-hydration, heartbeat-complete-reorder, stale-pending-settle specs.

Risks

Timing-based, not identity-proved: a redelivered message.start for the cancelled turn itself would now re-arm instead of being dropped (noted with a ponytail: ceiling comment in code). Making this airtight needs per-turn ids on gateway message.* events (the issue's option 3, a backend contract change, deliberately out of scope). A stale message.complete of the cancelled turn arriving AFTER a chained turn's start would settle onto the new bubble — same pre-existing ambiguity, unavoidable without event identity.

Related issues

Pre-cleared as non-occupying: PR #66485 (introduced the latch), PR #99667 (hydration recovery, no latch contact), PR #120601 (supersede scoping, no latch contact), open PR #104773 (clarify-request guard only), open PR #63292 (interrupted completions as metadata), open PR #124348 (Response-stopped marker). None touches the message.start guard.

Closes #122723

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 27, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Hermes Desktop silently swallows completed backend-chained turns after an interruption

2 participants