Skip to content

fix(desktop): slash commands target the user's chat, not a new session - #71605

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-e8b91735
Jul 25, 2026
Merged

fix(desktop): slash commands target the user's chat, not a new session#71605
teknium1 merged 1 commit into
mainfrom
hermes/hermes-e8b91735

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Infographic

slash commands find your chat

Summary

Desktop slash commands now run against the chat the user is looking at, instead of silently creating a new session to run against.

Reported symptom: start a goal in the desktop app, then type /goal status — it answers "No active goal" for a goal that is live.

Root cause: /goal state is persisted per-session in SessionDB (state_meta key goal:<session_id>), so the slash and submit pipelines must agree on which session id they mean. They didn't. slash.ts's ensureSessionId resolved with a bare hint || activeRef || createBackendSessionForSend(), so whenever the runtime binding was momentarily absent — profile swap, reconnect, orphan-reap, request timeout — it minted a new session and ran the command there. submit.ts already handles that case correctly by resuming the routed stored session on its owning profile (#55578, #67603).

This is a whole-class fix, not a /goal patch: every exec/rpc slash command (/usage, /status, /tools, /agents, …) shared the hole.

Changes

  • resolve-target-session.ts (new): one shared resolution ladder, per the "one resolver owns each policy" rule in apps/desktop/AGENTS.md.
    1. explicit runtime id from the caller (queue drain / tile)
    2. the live runtime ref — unless a durable route disagrees with it (a stale ref from the previous profile must not capture the command)
    3. session.resume on the routed (else selected) stored session, re-registered on the profile that owns it
    4. only a genuine new-chat draft creates a session
  • slash.ts: ensureSessionId delegates to the shared resolver; threads getRoutedStoredSessionId + getRuntimeIdForStoredSession.
  • index.ts: passes the two new deps through.
  • A targeted durable conversation whose runtime cannot be rebound returns null rather than forking — reporting that a command could not run beats running it against the wrong session.

Validation

Before After
/goal status with runtime binding absent slash.exec on brand-new-session → "No active goal" session.resumeslash.exec on the routed session
routed session un-rebindable forked a contextless chat no fork, command reports failure
desktop suite 2968 passed, 3 skipped, 0 failed
tsc --noEmit clean
eslint clean (1 pre-existing harness warning, untouched)

10 new regression tests (8 resolver-level, 2 at the /goal level reproducing the reported symptom).

Sabotage-verified — the tests were re-run with the resolver body reverted to the old hint || activeRef || createSession() ladder to prove they actually catch the bug rather than passing vacuously: 6 of 10 fail, including both /goal-level tests. Restored and re-verified green afterward.

Notes

`/goal status` reported "No active goal" for a goal that was live: the
desktop's slash pipeline resolved its target session differently than the
submit pipeline, so the command ran against a different session than the
chat on screen.

`/goal` state is persisted per-session in SessionDB (`state_meta` key
`goal:<session_id>`). slash.ts resolved with a bare
`hint || activeRef || createBackendSessionForSend()`, so whenever the
runtime binding was momentarily absent — profile swap, reconnect,
orphan-reap, request timeout — it silently MINTED A NEW SESSION and ran
there. submit.ts already handles this case by resuming the routed stored
session on its owning profile (#55578, #67603).

Extract that ladder into one shared resolver both pipelines use, per the
"one resolver owns each policy" rule in apps/desktop/AGENTS.md. This fixes
the whole class, not just `/goal`: every exec/rpc slash command
(`/usage`, `/status`, `/tools`, …) had the same hole.

A targeted durable conversation whose runtime cannot be rebound now
returns null instead of forking the chat — reporting that a command could
not run beats running it against the wrong session.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 25, 2026
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 344ef09

all good!

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/*) 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.

[Bug]: Desktop submit fails after /goal; local session row created but message never reaches backend

2 participants