Skip to content

fix(chat): namespace PTY attach token by resume target to fix session switching - #61107

Open
rblundon wants to merge 1 commit into
NousResearch:mainfrom
rblundon:fix/pty-attach-token-session-switch
Open

fix(chat): namespace PTY attach token by resume target to fix session switching#61107
rblundon wants to merge 1 commit into
NousResearch:mainfrom
rblundon:fix/pty-attach-token-session-switch

Conversation

@rblundon

@rblundon rblundon commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Selecting a previous session from the Chat tab's session list spawned a new terminal in the old session's context instead of resuming the selected one.

Root Cause

79f4f78fa introduced PTY keep-alive: the browser stores an attach token in localStorage["hermes.pty.token.chat"] and PTY_REGISTRY.attach_or_spawn() reuses the existing PTY process when the same token reconnects.

The token key was a single static string regardless of which session was open. When the user clicked a different session:

  1. ?resume=<new-id> changed → channel UUID regenerated → WebSocket effect re-ran
  2. ptyAttachToken(false) returned the same old token from localStorage
  3. Server found the existing PTY alive under that token → reattached to it
  4. The resume param in _resolve_chat_argv is only used when spawning a new PTY — on reattach it's ignored entirely
  5. Result: the old session's terminal appeared regardless of which session was selected

Fix

Namespace the localStorage key by resumeParam so each session ID gets its own stable PTY attach token:

// before
params.attach = ptyAttachToken(forceFresh);

// after
params.attach = ptyAttachToken(forceFresh, resumeParam ?? "");

ptyAttachToken now accepts an optional resumeKey and uses hermes.pty.token.chat.<resumeKey> as the storage key when non-empty. A fresh chat (no resume param) continues to use the bare key as before.

Keep-alive across refresh/transient disconnects is fully preserved — only switching to a different session now correctly spawns a new PTY.

Testing

  1. Start a chat session and send a message
  2. Navigate to the Sessions page and start another session
  3. Return to the Chat tab and use the session switcher to select the first session
  4. ✅ The first session's conversation now resumes correctly instead of opening a new terminal

Affected version

Introduced in 79f4f78fa feat(chat): persist attach token, reconnect on transient close

When the user selects a different session from the ChatSessionList,
`?resume=<id>` changes and the WebSocket effect re-runs. However,
`ptyAttachToken()` was reading a single static localStorage key
(`hermes.pty.token.chat`) regardless of which session was being opened.

`PTY_REGISTRY.attach_or_spawn()` found the existing PTY alive under that
token and reattached to it — completely ignoring the new `resume` param,
which is only used when spawning a *new* PTY. The result: clicking any
previous session in the Chat tab opened a new terminal in the *old*
session's context instead of resuming the selected one.

Fix: namespace the localStorage key by `resumeParam` so each session ID
gets its own stable attach token. A fresh chat (no resume) continues to
use the bare key as before. Cross-browser persistence of the keep-alive
identity is preserved within each conversation; switching conversations
always spawns a new PTY with the correct resume target.

Introduced by: 79f4f78 feat(chat): persist attach token, reconnect on transient close
@alt-glitch alt-glitch added type/bug Something isn't working comp/dashboard Web dashboard / control panel UI (dashboard/, landing) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P2 Medium — degraded but workaround exists duplicate This issue or pull request already exists labels Jul 8, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #61045 (earliest-open, created ~2.5h earlier). Both PRs change only web/src/pages/ChatPage.tsx and fix the same wrong-session-reattach bug the same way — add a resume-scope parameter to ptyAttachToken() and namespace the hermes.pty.token.chat localStorage key by the resume session id (the diffs are equivalent apart from the .-vs-: separator and comment wording). #61045 is the canonical lane. Related: #60745 fixes the same bug on the backend (web_server.py attach-token key) — a fuller fix may want both the frontend key scoping here and the backend scoping in #60745; #60515 introduced the keep-alive regression.

@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 isolating the keep-alive reattach path. The reported resume-switch defect is present on current main: ChatPage sends resume but retains one static attach token (web/src/pages/ChatPage.tsx:799-805), and the backend reuses a live PTY solely by that token (hermes_cli/web_server.py:15430-15453).

Problems

  • This scopes the browser key by resume target only. A profile switch still sends the same attach token to a registry that ignores profile; ChatPage passes profile independently at web/src/pages/ChatPage.tsx:809.
  • There is no regression coverage for distinct resume targets. Current coverage proves same-token reuse only in tests/test_pty_keepalive_ws.py:47-51.

Suggested changes

  • Scope the backend registry key by requested resume target and profile, and use that same key for detach.
  • Add same-resume reuse plus different-resume and different-profile spawn tests.

This is an automated hermes-sweeper review.

@@ -710,7 +717,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
// Keep-alive identity: reattach to this tab's living PTY across

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 scopes the browser token by resume target but not by scopedProfile. The request still sends profile independently, while current pty_ws keys the registry only by attach_token; switching profiles can therefore reattach a PTY running under the previous profile. Scope the server registry key by requested resume and profile (and use it for detach), with a profile-switch regression test.

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/dashboard Web dashboard / control panel UI (dashboard/, landing) duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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.

3 participants