Skip to content

fix(desktop): resync active chat after gateway reconnect - #44443

Open
mollusk wants to merge 1 commit into
NousResearch:mainfrom
mollusk:fix/desktop-sync-on-reconnect
Open

fix(desktop): resync active chat after gateway reconnect#44443
mollusk wants to merge 1 commit into
NousResearch:mainfrom
mollusk:fix/desktop-sync-on-reconnect

Conversation

@mollusk

@mollusk mollusk commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • rehydrate the active Desktop transcript after a successful gateway reconnect so missed WebSocket events are pulled from the persisted session instead of requiring an app restart
  • keep the existing sidebar/config refresh path and add the active transcript as a small optional reconnect callback
  • surface a recoverable boot error after prolonged reconnect failures so the UI exposes recovery controls while retries continue

Test Plan

  • npm run test:ui -- src/app/gateway/hooks/use-gateway-boot.test.tsx
  • npm run typecheck
  • npx eslint src/app/gateway/hooks/use-gateway-boot.ts src/app/gateway/hooks/use-gateway-boot.test.tsx src/app/desktop-controller.tsx

@liuhao1024

Copy link
Copy Markdown
Contributor

Verified this fix — the refreshActiveSession: () => hydrateFromStoredSession(3) callback correctly wires the gateway reconnect event to re-hydrate the active chat. The pattern matches how other gateway events (like refreshHermesConfig, refreshSessions) are already wired through useGatewayBoot. The test coverage for the refreshActiveSession prop is good — it verifies the callback is called on reconnect.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels Jun 11, 2026
@mollusk

mollusk commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@liuhao1024 What do we need to merge this? This would be a huge help, I've lost work enough to this already 🫠

After a successful reconnect, rehydrate the active transcript in addition
to the sidebar/config so deltas emitted while the WebSocket was down do
not require an app restart.
@mollusk
mollusk force-pushed the fix/desktop-sync-on-reconnect branch from 2efd125 to 53af4f5 Compare July 12, 2026 03:05

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for targeting a real Desktop recovery gap: current main refreshes config and session rows after the boot reconnect, but not the active transcript.

Problems

  • The callback is only reached from useGatewayBoot's reconnect loop. apps/desktop/src/app/gateway/hooks/use-gateway-request.ts:54-78 can reconnect the same primary socket after an RPC failure; its open state clears the boot loop timer at apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts:338-343, so this path never invokes the new refresh.
  • hydrateFromStoredSession() replaces messages after its REST request (apps/desktop/src/app/desktop-controller.tsx:508-515), while the socket event handler is already subscribed (apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts:359). A live event received while that request is pending can be overwritten by the older snapshot.

Suggested changes

  • Centralize the post-primary-reconnect reconciliation so both reconnect paths use it.
  • Guard the REST snapshot against newer live session updates, and cover both the request-reconnect path and the live-event-during-hydrate race.

Automated hermes-sweeper review.

// otherwise missing from the renderer until a full app restart.
await callbacksRef.current.refreshHermesConfig().catch(() => undefined)
await callbacksRef.current.refreshSessions().catch(() => undefined)
await callbacksRef.current.refreshActiveSession?.().catch(() => undefined)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook is not the only primary reconnect path: useGatewayRequest calls existing.connect() directly after a failed RPC (apps/desktop/src/app/gateway/hooks/use-gateway-request.ts:54-78). That successful open clears this hook's scheduled timer, so this callback never runs for that reconnect. Please move the reconciliation to a shared primary-reconnect transition or cover the request-triggered path too.

onGatewayReady: g => {
gatewayRef.current = g
},
refreshActiveSession: () => hydrateFromStoredSession(3),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hydrateFromStoredSession() awaits REST and then replaces messages (desktop-controller.tsx:508-515), but the WebSocket listener is already live after reconnect. A delta received while that request is pending can be overwritten by the older snapshot. Please make the hydrate response generation-aware or otherwise preserve newer live updates.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Three PRs address distinct Desktop gateway-state failure modes: #44443 rehydrates the active transcript after boot-loop reconnects, #50529 seeds the saved profile before cold-boot connection discovery, and #62624 deduplicates concurrent primary soft re-homes and renderer soft switches. Their diffs touch a shared gateway-boot area but do not implement the same fix.

Related pull requests

  • #44443 related — (+34/-1) — keep open for revision: the diff adds active-session rehydration only to the boot reconnect loop, so it misses primary-socket reconnects initiated by use-gateway-request; it also allows an in-flight REST snapshot to overwrite newer live events. This agrees with the keep_open review on #44443, whose two correctness concerns must be addressed before merge.
  • #50529 related — (+30/-4) — keep open for revision: the diff seeds the saved profile before desktop.getConnection(), addressing the cold-boot routing cause, but duplicates profile-adoption logic instead of using the current helper and its test asserts only after all asynchronous work has flushed. This agrees with the keep_open review on #50529: rebase onto adoptPrimaryProfile() and prove the ordering with a deferred getConnection().
  • #62624 related — (+81/-4) — merge: the diff serializes concurrent global/primary applies through one in-flight re-home promise and independently guards renderer softSwitch() reentrancy; its regression test verifies that duplicate applied events cause only one additional connection lookup. The contributor keep_open review on #62624 found no substantive correctness issue and rated the change highly salvageable.

Suggested consolidation

Merge #62624 after bringing it current and passing the applicable checks; it directly and comprehensively fixes the concurrent soft-rehome race. Keep #44443 and #50529 open for the specific revisions identified in their contributor reviews; none of #44443, #50529, or #62624 should be closed as a duplicate because they address separate causes.

Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 15 kB of PR diffs, 7 kB of issue/PR text, 5 kB of discussion (9 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages 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.

5 participants