Skip to content

fix(dashboard): resume chat in the session's owning profile - #48049

Open
cyb0rgk1tty wants to merge 1 commit into
NousResearch:mainfrom
cyb0rgk1tty:fix/chat-resume-cross-profile-404
Open

fix(dashboard): resume chat in the session's owning profile#48049
cyb0rgk1tty wants to merge 1 commit into
NousResearch:mainfrom
cyb0rgk1tty:fix/chat-resume-cross-profile-404

Conversation

@cyb0rgk1tty

Copy link
Copy Markdown
Contributor

What

Reopening a dashboard chat that belongs to a different profile than the one currently selected in the profile switcher fails with "session not found." The desktop app hits this too: it restores its last /chat?resume=<id> URL on launch, which carries no ?profile=, so the resume is scoped to whatever profile happens to be sticky-active.

Root cause: sessions are stored in per-profile state.db files, but the resume path resolved them only against the process/active profile:

  • _session_latest_descendant() always opened the process db, and /api/sessions/{id}/latest-descendant took no profile argument.
  • _resolve_chat_argv() bound HERMES_HOME from the switcher hint, so the spawned TUI reopened the wrong state.db and 404'd.

Fix

  • _session_latest_descendant(session_id, profile=None) opens the requested profile's db and, on a miss, falls back to locating the owning profile and walking the compression chain there. Session ids are globally unique, so this is safe.
  • /api/sessions/{id}/latest-descendant accepts ?profile=, matching the sibling read endpoints (/messages, detail).
  • _resolve_chat_argv() resolves a resume target's owning profile and pins HERMES_HOME to it explicitly (including the default home), so the spawned TUI reopens the correct state.db regardless of the hint. Named owners still spawn their own gateway; default-owned resumes keep the in-memory gateway attach.
  • New _find_session_profile(): read-only point-lookup across profiles (no table scan, no descendant walk), used only on the cold path.

Repro

  1. Have a session in the default profile and a non-default profile active in the switcher (or open a desktop window whose restored URL is /chat?resume=<default-session-id> with no ?profile=).
  2. Resume that session. Before: session not found. After: resumes correctly.

Scope / relation to other PRs

This is the resume / chat-PTY half of cross-profile session handling. It complements #44162 (the transcript-load path — getSessionMessages, for #44147); the two don't overlap in code. A frontend change could additionally thread the owning profile into the resume URL to skip the lookup, but the server-side fix makes resume correct even for bookmarked / desktop-restored URLs that carry no profile.

Testing

  • New regression tests in tests/hermes_cli/test_web_server_profile_unification.py::TestProfileScopedChatResume: owner lookup; latest-descendant resolution in / without / with-wrong profile; the endpoint's ?profile= honoring + no-profile fallback; and _resolve_chat_argv binding the owner for both named- and default-owned resumes.
  • scripts/run_tests.sh tests/hermes_cli/test_web_server.py tests/hermes_cli/test_web_server_profile_unification.py — pass (3 unrelated, pre-existing failures in the status/update-hermes endpoints reproduce on main without this change).
  • scripts/check-windows-footguns.py hermes_cli/web_server.py — clean.

Sessions live in per-profile state.db files, but the dashboard chat
resume path resolved them only against the process/active profile.
Reopening a chat owned by a different profile than the one currently
selected — including a bare desktop-restored /chat?resume=<id> URL that
carries no ?profile= — 404'd with "session not found".

- _session_latest_descendant() opens the requested profile's db and, on
  a miss, falls back to locating the owning profile (session ids are
  globally unique), so the compression chain is walked where the session
  actually lives.
- /api/sessions/{id}/latest-descendant accepts ?profile=, matching the
  sibling read endpoints.
- _resolve_chat_argv() resolves a resume target's owning profile and pins
  HERMES_HOME to it explicitly (including the default home), so the TUI
  reopens the correct state.db regardless of the switcher hint.
- add _find_session_profile(): read-only point-lookup across profiles.

Complements NousResearch#44162 (transcript-load path); this covers the resume/PTY
path. Adds regression tests in test_web_server_profile_unification.py.
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels Jun 17, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #44162 (the transcript-load half — getSessionMessages, for #44147). This PR is the resume / chat-PTY half of cross-profile session handling; per the description the two don't overlap in code, so they're complementary rather than duplicates.

@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state and removed comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery labels Jun 26, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for isolating the dashboard PTY resume path. The current-head premise is still visible: the PTY forwards profile into _resolve_chat_argv (hermes_cli/web_server.py:15483-15512), which scopes both descendant lookup and HERMES_HOME to that one profile (hermes_cli/web_server.py:14618-14631).

Problems

  • The proposed owner fallback relies on globally unique session IDs, but current main deliberately creates shared-root in both the default and worker profile (tests/hermes_cli/test_web_server.py:1303-1328). A first-match _find_session_profile() can therefore resume the wrong session or compression lineage.
  • Main already uses explicit profile scoping in the dashboard path (web/src/pages/ChatPage.tsx:904-915), and commit 6015ee5d repaired descendant lookup to use that scoped database. Overriding an explicit scope based on an ambiguous ID would conflict with that routing model.

Suggested changes

  • Carry an explicit profile with durable resume links/restored state instead of discovering an owner from a raw session ID.
  • Add a duplicate-ID cross-profile regression proving that a resume never silently crosses profile boundaries.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history area/profiles Multi-profile isolation, HERMES_HOME scoping labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/dashboard Web dashboard / control panel UI (dashboard/, landing) 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-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