Skip to content

fix(desktop): fall back to the engine's most recent session when the boot-restored chat is dead - #60607

Closed
Vencounsel wants to merge 1 commit into
NousResearch:mainfrom
Vencounsel:fix/desktop-validate-restored-session
Closed

fix(desktop): fall back to the engine's most recent session when the boot-restored chat is dead#60607
Vencounsel wants to merge 1 commit into
NousResearch:mainfrom
Vencounsel:fix/desktop-validate-restored-session

Conversation

@Vencounsel

Copy link
Copy Markdown

What does this PR do?

On cold start the desktop app navigates straight to the localStorage-pinned hermes.desktop.lastSessionId without validating it against the backend. When that pin is stale (the session was deleted or rotated while the app was closed), the boot strands on the resume-error screen for a chat the user never chose; the engine's actual most recent session is never considered.

This PR keeps the optimistic restore — the happy path still costs no round trip and no new-chat flash — and adds a fallback using the existing resume-exhausted latch as the staleness verdict: the latch only arms while the gateway is open, after the bounded resume retries gave up (use-route-resume.ts), so it cleanly distinguishes a dead pin from a down backend. No extra validation RPC, and no false positives from checking membership in the paginated session list.

When the latch fires for the boot-restored session while it's still the routed one, the controller replace-navigates to the engine's most recent session — preferring an is_active session (a runtime is live on it right now), then newest last_active, skipping archived rows and the dead session's own lineage — or to the new-chat route when nothing qualifies. The dead pin is still cleared, as before.

Deliberately narrow: sessions the user opened by hand keep the explicit error + Retry UI — the fallback fires only for the automatic boot restore, so it never yanks a session the user explicitly chose out from under them.

Related Issue

Fixes #60541

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • apps/desktop/src/app/desktop-controller.tsx: track which session id the cold-boot restore navigated to (bootRestoredSessionIdRef); on resume-exhaustion of exactly that session while routed to it, replace-navigate to pickMostRecentSessionId($sessions, deadId) or the new-chat route.
  • apps/desktop/src/app/desktop-controller-utils.ts: new pure helper pickMostRecentSessionId() (active-first, then last_active; skips archived and the dead lineage — id matching mirrors sessionMatchesStoredId()).
  • apps/desktop/src/app/desktop-controller-utils.test.ts: six cases — empty list, newest-by-last_active, active-over-newer-inactive, archived skipped, exclusion by id and lineage root, all-excluded → null.

How to Test

  1. cd apps/desktop && npx vitest run --environment jsdom src/app/desktop-controller-utils.test.ts — 10 pass (4 existing + 6 new; the new ones fail on current main).
  2. Manual reproduction: open a chat in the desktop app, quit, delete that session from the CLI (hermes sessions tooling) or another surface, relaunch.
    • Current main: boot lands on the resume-error screen for the deleted session.
    • This branch: after the bounded retries exhaust, the window jumps to the engine's most recent session (or new-chat when none), and the stale pin is replaced by the new session id.
  3. Deliberate-open path unchanged: open an old dead session from the sidebar → error + Retry UI still shown, no auto-navigation.
  4. npm run typecheck and npx eslint on the touched files pass.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(desktop):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q: no Python changes in this diff; full-suite comparison against a pristine main checkout on the same machine shows no new failures (environment-dependent failures reproduce identically on base)
  • I've added tests for my changes
  • I've tested on my platform: macOS 26 (Darwin 25.5), Node 22.23.0
  • Full desktop unit suite (vitest run --environment jsdom): no new failures vs. a pristine checkout of the same base commit (24 pre-existing environment-dependent failures on both, identical set)

Documentation & Housekeeping

  • Comments updated in the touched effects — or N/A
  • cli-config.yaml.example — N/A
  • CONTRIBUTING.md / AGENTS.md — N/A
  • Cross-platform impact: renderer-only
  • Tool descriptions/schemas — N/A

…boot-restored chat is dead

Cold start navigates to the localStorage-pinned hermes.desktop.lastSessionId
without validating it against the backend. When that id is stale (the session
was deleted or rotated while the app was closed), the boot strands on the
resume-error screen; the engine's actual most recent session is never
considered.

Keep the optimistic restore — the happy path costs no round trip and no
new-chat flash — and use the existing resume-exhausted latch as the
staleness verdict: it only arms while the gateway is open, after the bounded
resume retries gave up, so it distinguishes a dead pin from a down backend
(no extra validation RPC, no false positives from the paginated session
list). When the latch fires for the boot-restored session while it is still
the routed one, replace-navigate to the engine's most recent session —
preferring an is_active session, then newest last_active, skipping archived
rows and the dead session's own lineage — or to the new-chat route when
nothing qualifies.

Sessions the user opened deliberately are untouched: those keep the explicit
error + Retry UI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have 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.

Desktop cold boot restores a stale pinned session id instead of falling back to the engine's most recent session

2 participants