Skip to content

fix(dashboard): force fresh PTY spawn when resume target changes - #61313

Open
sterman wants to merge 1 commit into
NousResearch:mainfrom
sterman:fix/dashboard-keepalive-pty-resume-session-history
Open

fix(dashboard): force fresh PTY spawn when resume target changes#61313
sterman wants to merge 1 commit into
NousResearch:mainfrom
sterman:fix/dashboard-keepalive-pty-resume-session-history

Conversation

@sterman

@sterman sterman commented Jul 9, 2026

Copy link
Copy Markdown

Summary

When the user picks a different session from the ChatSessionList sidebar or the Sessions page's play button, the embedded TUI was not loading the newly selected session's history. Instead it continued showing the previous session (or a fresh/empty one), making the "Resume in Chat" feature unusable for switching between conversations.

Root Cause

The keep-alive PTY mechanism (introduced in v0.18.0) reattaches to the existing live PTY process when the same ?attach= token is used. In this case _resolve_chat_argv() is never called, so HERMES_TUI_RESUME stays at its original value and the TUI never receives the new session id.

Specifically, PtySessionRegistry.attach_or_spawn() at web_server.py returns the existing session when the attach token matches, bypassing the spawn path that would set the correct env var.

Fix

Track the previous ?resume= query param value with a useRef. When it changes between effect runs (user navigated to a different session), set forceFresh = true so the new WebSocket connection sends fresh=1, forcing the backend to spawn a fresh PTY with the correct HERMES_TUI_RESUME.

Before: URL changed to /chat?resume=X but the keep-alive PTY was reattached with stale env vars, loading the wrong session.

After: Resume target change triggers fresh=1 → new PTY spawned with HERMES_TUI_RESUME=Xsession.resume RPC loads the correct history.

Related

Fixes #19085 — dashboard Sessions → Chat resume does not reliably load the selected historical session.

When the user picks a different session from the ChatSessionList sidebar
or the Sessions page's play button, the existing keep-alive PTY (same
?attach= token) was reattached without updating HERMES_TUI_RESUME in the
child process environment. The TUI continued with the old session (or
none) instead of loading the newly selected session's history.

Root cause: PtySessionRegistry.attach_or_spawn() returns the existing
live PTY when the attach token matches, so _resolve_chat_argv() is never
called and the env var stays stale.

Fix: track the previous ?resume= query param with a ref, and when it
changes, set forceFresh = true so the new WebSocket connection sends
fresh=1, forcing the backend to spawn a new PTY with the correct
HERMES_TUI_RESUME.

Fixes NousResearch#19085
@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 labels Jul 9, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #60745 (fixes the same wrong-session-on-resume symptom by scoping the attach token by session on the backend in hermes_cli/web_server.py), #19085 (the closed target issue).

This PR fixes it on the frontend — detecting a ?resume= change and forcing fresh=1 so a new PTY spawns with the correct HERMES_TUI_RESUME. Same goal, different layer/mechanism (frontend force-fresh vs backend attach-token scoping) — not a duplicate. A maintainer should decide whether the frontend fix, the backend fix (#60745), or both are wanted.

@mguttmann

Copy link
Copy Markdown
Contributor

We are hitting exactly this in a production deployment and can confirm the root-cause analysis.

Environment: Hermes v0.18.2 (2026.7.7.2), Ubuntu 24.04, dashboard bound to 127.0.0.1 behind an SSH tunnel, Node 20.18.1, gateway + dashboard as systemd user services.

Symptom (matches this PR's summary 1:1): clicking any session in the Chat sidebar — or the Sessions page play button — never loads the selected session's history. The embedded TUI always shows a fresh/empty session (banner screen), while the status bar correctly reports the live PTY count. Switching targets repeatedly makes no difference; the keep-alive PTY visibly re-attaches to the previous spawn instead of respawning for the new resume target. Session transcripts are fine (the Sessions page renders them), so this is purely the attach/spawn path.

For us this makes "Resume in Chat" unusable for reviewing an agent's past runs, which is a daily workflow in our QA setup (the dashboard is our team's read-only window into an autonomous testing agent). We currently work around it via the Sessions page transcript view.

Happy to test a build of this branch against our deployment and report back if that helps move it along. Thanks for the thorough root-cause writeup — the fix approach (forcing a fresh PTY spawn when the resume target changes) looks right to us.

@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 current-main defect is real: web/src/pages/ChatPage.tsx:799-805 sends a changed resume alongside the stable attach token, while hermes_cli/web_server.py:15450-15454 looks up the live PTY solely by that token.

Problems

  • The proposed fresh=1 route cannot resume the chosen session. hermes_cli/web_server.py:15402-15405 deliberately sets resume = None for fresh requests, and HERMES_TUI_RESUME is only set when resume is non-empty at hermes_cli/web_server.py:14526-14536. The resulting child is blank, not resumed.
  • No regression test covers changed resume targets. tests/test_pty_keepalive_ws.py:7-52 currently covers only same-token reuse.

Suggested changes

  • Keep "spawn a different PTY" separate from the existing fresh meaning of "start blank"—for example, scope the backend attach identity by raw resume and profile as in related #60745, or rotate the attach token without sending fresh=1.
  • Add a two-target keep-alive regression test that verifies the second spawn receives its selected resume target.

Automated hermes-sweeper review.

// the newly selected session's history.
const resumeChanged = resumeParam !== prevResumeParamRef.current;
if (resumeChanged && !forceFresh) {
forceFresh = true;

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.

fresh=1 is not a generic respawn flag: current pty_ws clears resume when fresh is set (hermes_cli/web_server.py:15402-15405). This will spawn a blank TUI rather than one with HERMES_TUI_RESUME set to the newly selected session. Please separate attach-token rotation/new-spawn behavior from the established blank-session fresh semantic.

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

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P2 Medium — degraded but workaround exists 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.

dashboard Sessions -> Chat resume does not reliably load the selected historical session

4 participants