fix(desktop): salvage inflight-journal cluster — skip stale duplicate folds + bound journal persistence - #86590
Merged
Merged
Conversation
Contributor
૮ >ﻌ< ა ci reviewran on bfd423d — fix(desktop): clear the stale stream target on the assistant
|
The desktop journal synchronously read, parsed, cloned, and rewrote one aggregate localStorage value while streamed turns were repainting. Large tool results and multi-session state could therefore block the renderer and leave the app unresponsive, while the existing macOS diagnostic path lacked a real native hide/restore regression check. Store bounded recovery projections under per-session keys, migrate legacy v1 data once, isolate quota and storage failures, and preserve the newest recoverable tail without allowing oversized writes to replace valid state. Add a real Electron/CDP macOS-arm64 A/B harness with native visibility control, renderer heartbeat and Settings/composer/transcript checks, plus focused regressions. Keep bulk tool payloads, diagnostics, and the existing recovery merge behavior out of the hot path. Fixes #63047
The inflight journal regression test always spied on Storage.prototype, but Node 26's jsdom setup can provide a plain in-memory localStorage fallback. That left the test unable to observe the setItem call in CI even though the per-session journal write was correct. Select the native window.Storage prototype when available and otherwise spy on the active localStorage object, preserving the assertion across both storage implementations. Refs #82832
The inflight-turn journal can outlive the turn it recorded (reclaim, reconnect or restart races skip the settle that clears it). On session resume the fold then re-appends journaled assistant rows to a transcript that already holds the committed replies, so the conversation ends with duplicate answers in scrambled order. The fold also carried the stale entry's streamId onto the resumed state on an idle resume, which kept the journal entry alive (persistInFlightTurnState only clears when streamId is null) and re-folded the same tail on every open. Detect text-level staleness before the append path: when every recoverable journaled assistant row already exists as committed text in the base transcript, treat the entry as caught up and clear it. Only keep a stream target when the resumed session is genuinely running (keepPending), so an idle resume self-heals instead of re-folding.
…end path too Sibling site of the idle-resume rule from the stale-fold fix: the assistant-tail append exit (user row persisted, no projection row) still carried the journal's streamId onto a not-running resume, which kept the journal entry alive (persistInFlightTurnState only clears when streamId is null) and re-folded the same tail on every open. Apply the same keepPending gate and pin it with a regression assertion. Refs #85308
teknium1
force-pushed
the
salvage/desktop-inflight-journal
branch
from
August 15, 2026 03:30
db1e360 to
bfd423d
Compare
teknium1
enabled auto-merge (rebase)
August 15, 2026 03:40
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Salvages the Desktop inflight-journal duplicate-answer cluster — PR #84021 by @Nicolas-Formenton and PR #82832 by @egilewski — onto one green branch against current
main, with contributor authorship preserved via cherry-pick.Fixes #85308
Fixes #63047
What this combines
1. Bound inflight journal persistence (#63047) — from #82832, author @egilewski
The desktop journal synchronously read, parsed, cloned, and rewrote one aggregate localStorage value while streamed turns were repainting. Large tool results and multi-session state could block the renderer and leave the app completely unresponsive (~5 messages on the reporter's machine, Settings included).
repro:short-session-hang) with native visibility control, renderer heartbeat, and Settings/composer/transcript checks, plus focused regressions.Storage.prototype) so the regression is observable under Node 26's jsdom fallback.2. Skip stale journal duplicate folds (#85308) — from #84021, author @Nicolas-Formenton
The inflight-turn journal can outlive the turn it recorded (reclaim, reconnect, or restart races skip the settle that clears it). On session resume the fold then re-appended journaled assistant rows to a transcript that already held the committed replies — duplicate answers in scrambled order — and carried the stale entry's
streamIdonto the resumed state, which kept the journal alive (persistInFlightTurnStateonly clears whenstreamIdis null) and re-folded the same tail on every open.journalTailAlreadyCommitted(): when no base user row matches the tail, drop the fold if every recoverable assistant row's text is already committed in the transcript (error-only rows keep the conservative append path).keepPending: true) keeps a live stream target; an idle resume clearsstreamIdso the journal self-heals instead of re-folding forever.3. Sibling-site widening (maintainer follow-up)
The assistant-tail append exit path (user row persisted, no projection row) still carried the journal's
streamIdon a not-running resume — the same self-perpetuating-fold bug class. Applied the samekeepPendinggate there and pinned it with a regression assertion.Verification
npx vitest run src/lib/inflight-turn-journal.test.ts— 38/38 pass (includes both PRs' regression suites merged).node --test scripts/run-short-session-hang-repro.test.mjs— 7/7 pass.npx tsc --noEmiton apps/desktop — clean.npx eslinton all touched JS/TS files — clean.origin/mainwith one clean auto-merge and zero manual conflicts (the journal file has not moved; theuse-session-state-cache.ts/use-session-actionscall sites are unchanged).Credit
Infographic