Skip to content

fix(desktop): preserve remote session routing - #93451

Closed
lukeroberts wants to merge 1 commit into
NousResearch:mainfrom
lukeroberts:fix/remote-session-routing
Closed

lukeroberts wants to merge 1 commit into
NousResearch:mainfrom
lukeroberts:fix/remote-session-routing

Conversation

@lukeroberts

Copy link
Copy Markdown

Summary

  • preserve the active registry connection as the owner fallback when a remote session row has no connection_id
  • keep stored-session reads, session.resume, and follow-up prompt.submit on that same (connection, profile) route
  • add regression coverage for both opening an untagged remote row and sending the next prompt

Problem

The unified Sessions list normally tags registry-owned rows with connection_id, but a direct connection switch can reload rows without that tag. In a multi-gateway setup, an untagged remote row whose profile is default can then be resolved as only default.

That profile name is not globally unique: both the local gateway and an SSH/remote gateway can own a default profile. The renderer consequently falls back to the ambient/local dispatcher. The remote session still exists, but session.resume reaches the local backend and fails with resume failed, session not found. Even after a successful resume, the prompt pipeline had its own ambient dispatcher and could fail the next message with session not found for the same reason.

This can affect other users with multiple registered gateways and legacy/untagged session rows, particularly local + SSH/remote configurations where both sides use the default profile name.

The behavior became reachable when the unified cross-gateway Sessions list was introduced in d354af5 (feat(desktop): unified Sessions list shows every connected gateway's chats (#88880), Aug 18, 2026). Later profile-routing fixes covered explicit owner metadata, but not this connection-less fallback or the separate prompt submission path.

Fix

For an untagged row, use the active registry connection as an ambient owner only when the current connection is remote. Explicit owner hints and persisted connection_id values continue to take precedence, and local mode is unchanged.

The resolved connection is then used consistently for:

  • stored-session REST scope
  • session.resume / session.activate / session.usage
  • follow-up prompt.submit

Test plan

  • npx vitest run src/app/session/hooks/use-prompt-actions/index.test.tsx src/app/session/hooks/use-session-actions.test.tsx (183 passed)
  • npm run typecheck
  • ESLint on all four modified Desktop files (no errors; one pre-existing exhaustive-deps warning in the prompt test harness)
  • git diff --check
  • npm run pack
  • Live local + SSH gateway verification: reopened the original affected remote session, submitted a follow-up prompt, and confirmed both the prompt and response were persisted in the remote gateway's state database

@lukeroberts
lukeroberts force-pushed the fix/remote-session-routing branch from 0a8598b to 4bcd9c0 Compare August 24, 2026 02:35
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 24, 2026
@rollingonchrome

Copy link
Copy Markdown

I reproduced the same local+SSH default-profile failure against this branch on a Wintermute thin client: the previously failing remote stored session resumed successfully after #93451, but submitRewindPrompt still used the ambient dispatcher for regenerate/edit/restore.

I opened a dependent PR that completes the rewind/resubmit path, keeps one captured owner through history lookup, interrupt, session-not-found recovery, busy retry, and stale-target resume, and aborts edit recovery if foreground selection changes while resume is pending:

lukeroberts#1

Verification on the dependent diff:

  • three red→green owner-routing regression tests
  • 219 focused tests passed
  • full Desktop UI suite: 5,618 passed across 585 files
  • typecheck passed
  • ESLint: 0 errors; one pre-existing Harness warning
  • macOS arm64 package build passed

The existing affected remote session remained read-only; resume succeeded without Session not found. A disposable live prompt attempt was interrupted by a separate full Desktop process exit before any prompt.submit frame was emitted, which is disclosed in the dependent PR rather than presented as successful end-to-end prompt/regenerate validation.

@jakewvincent

Copy link
Copy Markdown

Confirmed as an independent, high-impact real-world case of this SSH/remote routing failure.

Environment: macOS Hermes Desktop connected primarily through a registered SSH gateway to a Linux host. Both the Mac client and gateway host were updated together and are on the same current Hermes generation (the gateway checkout is current 057dcdf).

Observed behavior:

  • Opening conversations from both Pinned and the ordinary session list usually renders the persisted transcript, then surfaces Session not found; the conversation cannot be continued.
  • The durable rows and transcripts are still present in the remote/default state.db (archived=0, hidden=0), so this is not deletion or corruption.
  • A small subset of otherwise similar sessions still works, with no durable-row property that explains the split; that is consistent with missing/mis-scoped ephemeral connection/runtime bindings rather than bad session data.

This makes the Desktop effectively unusable for an SSH-gateway-first workflow: the normal way to return to most prior conversations is broken even though their histories are intact. The local + SSH/remote default-profile ambiguity described in this PR is a strong match.

Happy to provide redacted gateway logs or help validate this branch against the affected setup.

teknium1 added a commit that referenced this pull request Aug 26, 2026
… mock traffic

Follow-up for salvaged PR #93451: main grew branchStoredSession tests (#93444)
that drive the same hoisted requestGatewayForProfile/ForAgent mocks, so the
not-called assertions in the warm-cache describe saw stale recorded calls.
teknium1 pushed a commit that referenced this pull request Aug 26, 2026
…nsport-error recovery

Salvage of #94192's unique work (owner-hardening portions that overlap
the class-1 branch — #94824/#93451 seams — and out-of-cluster #94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of #91365-adjacent routing gaps.
Salvaged (partial) from #94192.
teknium1 pushed a commit that referenced this pull request Aug 26, 2026
…nsport-error recovery

Salvage of #94192's unique work (owner-hardening portions that overlap
the class-1 branch — #94824/#93451 seams — and out-of-cluster #94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of #91365-adjacent routing gaps.
Salvaged (partial) from #94192.
teknium1 pushed a commit that referenced this pull request Aug 26, 2026
…nsport-error recovery

Salvage of #94192's unique work (owner-hardening portions that overlap
the class-1 branch — #94824/#93451 seams — and out-of-cluster #94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of #91365-adjacent routing gaps.
Salvaged (partial) from #94192.
@teknium1

Copy link
Copy Markdown
Collaborator

Merged via #95080 at 682a34a — your commit was cherry-picked into the consolidated salvage PR with authorship preserved, so this work carries your name in git history.

Thanks for preserving remote session routing — your fix kept sessions pinned to the connection that owns them instead of drifting to the ambient source.

Closing this original PR now that the consolidated branch has landed on main. Thanks for contributing to the desktop multi-gateway campaign (tracker: #94724).

@teknium1 teknium1 closed this Aug 26, 2026
and7777 pushed a commit to and7777/hermes-agent that referenced this pull request Aug 27, 2026
… mock traffic

Follow-up for salvaged PR NousResearch#93451: main grew branchStoredSession tests (NousResearch#93444)
that drive the same hoisted requestGatewayForProfile/ForAgent mocks, so the
not-called assertions in the warm-cache describe saw stale recorded calls.
and7777 pushed a commit to and7777/hermes-agent that referenced this pull request Aug 27, 2026
…nsport-error recovery

Salvage of NousResearch#94192's unique work (owner-hardening portions that overlap
the class-1 branch — NousResearch#94824/NousResearch#93451 seams — and out-of-cluster NousResearch#94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of NousResearch#91365-adjacent routing gaps.
Salvaged (partial) from NousResearch#94192.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
… mock traffic

Follow-up for salvaged PR NousResearch#93451: main grew branchStoredSession tests (NousResearch#93444)
that drive the same hoisted requestGatewayForProfile/ForAgent mocks, so the
not-called assertions in the warm-cache describe saw stale recorded calls.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…nsport-error recovery

Salvage of NousResearch#94192's unique work (owner-hardening portions that overlap
the class-1 branch — NousResearch#94824/NousResearch#93451 seams — and out-of-cluster NousResearch#94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of NousResearch#91365-adjacent routing gaps.
Salvaged (partial) from NousResearch#94192.
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/*) P2 Medium — degraded but workaround exists 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.

5 participants