Skip to content

fix(desktop): hold create guard until route catches new session - #66610

Closed
stantheman0128 wants to merge 5 commits into
NousResearch:mainfrom
stantheman0128:fix/66057-new-session-jumpback
Closed

fix(desktop): hold create guard until route catches new session#66610
stantheman0128 wants to merge 5 commits into
NousResearch:mainfrom
stantheman0128:fix/66057-new-session-jumpback

Conversation

@stantheman0128

Copy link
Copy Markdown
Contributor

Summary

Closes #66057

Credit: Stan Shih (@stantheman0128). Developed with AI assistance (Cursor / Grok).

After New Session, the first send could flash the new chat then jump back to the previous one. createBackendSessionForSend updated selection refs and navigated, then cleared creatingSessionRef with setTimeout(0). use-route-resume could then treat the still-old route as stuck and resumeSession the previous chat.

Changes

  • Keep creatingSessionRef true until the route and selection agree on the created (or forked) stored session id
  • Belt in use-route-resume: if selection/runtime already moved ahead of a stale route, do not resume the old route
  • Escape hatches so the guard cannot stick forever: navigate throw, route leaving for a non-pending id, fork catch, and a 3s safety timeout

Related but different: open #66376 covers stale resume at New Chat draft start, not this first-send create-route race.

Verification

`
npx vitest run --project ui src/app/session/hooks/use-route-resume.test.tsx src/app/session/hooks/use-session-actions.test.tsx

Test Files 2 passed | Tests 36 passed

`

`
python scripts/check-windows-footguns.py --diff HEAD~2

clean

`

Made with Cursor

@alt-glitch alt-glitch added type/bug Something isn't working 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 labels Jul 18, 2026

@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 tracing the create/route race; current main still has the delayed guard release at apps/desktop/src/app/session/hooks/use-session-actions/index.ts:344-348, and its stale-route self-heal can resume the old route at apps/desktop/src/app/session/hooks/use-route-resume.ts:137-150.

Problems

  • The new suppression at apps/desktop/src/app/session/hooks/use-route-resume.ts:150 remains active after the proposed 3s timeout if navigation never moves from A to B: selection and active refs remain B, so stuckOnRoutedSession stays false. That makes the claimed timeout recovery ineffective; ChatView keeps the route/selection mismatch in its loading state (apps/desktop/src/app/chat/index.tsx:281-315).

Suggested changes

  • Tie this suppression to an active pending-create state, or reconcile route/selection when the timeout releases the guard. Add a regression test covering a route that never catches up and asserting recovery, not only guard release.

Automated hermes-sweeper review.

const stuckOnRoutedSession =
routedSessionId !== selectedStoredSessionIdRef.current &&
!freshDraftReady &&
!selectionMovedAheadOfRoute

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.

The 3s release does not restore recovery if navigation never reaches B: the route remains A while selected/active refs remain B, so this condition stays true and stuckOnRoutedSession never resumes A. Scope this suppression to pending creation or reconcile state when the timeout fires; add a test for that composed timeout path.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
@stantheman0128

Copy link
Copy Markdown
Contributor Author

Thanks for flagging the timeout recovery hole.

You were right: clearing creatingSessionRef after 3s did not help when selection/active stayed on B and the route never left A. selectionMovedAheadOfRoute kept stuckOnRoutedSession false, so ChatView stayed on the route/selection mismatch loader.

Pushed 270f9ecb8 on this branch (Stan Shih / stantheman0128, with Cursor assist):

  • Scope that suppression to an active pending-create hold (creatingSessionRef) only
  • On the safety timeout, retry navigate to the pending session, then release the guard so route resume can still self-heal if the router stays stuck
  • Regression test: never-catches-up shape asserts resumeSession(A) (recovery), not only guard release; the timeout test also asserts the retry navigate

Evidence:

npx vitest run src/app/session/hooks/use-route-resume.test.tsx src/app/session/hooks/use-session-actions.test.tsx

 RUN  v4.1.9

 Test Files  2 passed (2)
      Tests  37 passed (37)

@teknium1 teknium1 added the area/sessions Session lifecycle, resume, persistence, history label Jul 19, 2026
@stantheman0128
stantheman0128 force-pushed the fix/66057-new-session-jumpback branch from 270f9ec to 09423ec Compare July 23, 2026 07:28
stantheman0128 and others added 5 commits July 27, 2026 11:53
…Research#66057)

Stop setTimeout(0) from clearing creatingSessionRef before HashRouter
lands on the created stored id, and ignore stale-route self-heal when
selection already moved to the new runtime. Prevents resumeSession(A)
jump-back after the first send on a new chat.

Credit: Stan Shih (stantheman0128); AI Cursor/Grok
Co-authored-by: Cursor <cursoragent@cursor.com>
Release creatingSessionRef when navigate throws, when the route leaves the
pre-create location for a non-pending id, or after a 3s safety timeout so
a stuck router cannot block resumes forever. Same clear on forkBranch catch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Scope selectionMovedAheadOfRoute to the pending-create hold and retry
navigate when the 3s safety timeout fires so ChatView can leave a stuck
A-route/B-selection loading state instead of suppressing resume forever.

Co-authored-by: Cursor <cursoragent@cursor.com>
…trip

An editing step on Windows decoded these files as cp950 and re-encoded them
as ASCII, which turned every em dash, arrow and curly quote into "??" and
swallowed the byte that followed it. The result was 34 comment lines showing
up as unrelated changes in the diff.

Restored each line by matching its mangled form against the merge-base blob,
plus four comments added by this branch that had no upstream original. Only
lines with a unique upstream match were rewritten, so nullish coalescing is
untouched. No code changes.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…sses

SessionActionsOptions gained routedSessionId and getRoutedStoredSessionId in
this branch, but StoredIdRotationHarness only passed the getter and
GuardHarness only passed the reactive id, so tsc failed on both call sites.
Each harness already holds the route it is simulating; feed that to the
missing option rather than a stub.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@stantheman0128

Copy link
Copy Markdown
Contributor Author

Closing as author to bring our open PRs on hermes-agent back within a healthy throttle (we had 10 open with only sweeper keep_open and no concrete maintainer change requests for days).

Keeping three Windows-focused PRs open for now:

Happy to reopen this one if a maintainer wants it prioritized. Thanks for the patience.

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/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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(desktop): new session sends prompt then jumps back to old session

3 participants