fix(desktop): bind a tab's slash command to its own session - #71805
Merged
Conversation
A slash command runs against the session `resolveTargetSessionId` picks, which is routinely not the session on screen — a tile, a route rebind, or a session created by the call itself. Both prompt pipelines gated on `busyRef`, the FOREGROUND view's busy flag, so one session's send was gated on another session's turn: a stale foreground `true` (a warm resume of a still-running chat leaves one behind) parked an idle session's command on the composer queue and reported "session busy" about a session doing nothing. The converse also leaked — a background send could fire mid-turn while the foreground happened to be idle. Read the published per-session state instead, falling back to the foreground flag only when the target has no state yet (a just-minted session whose first publish hasn't landed). One shared resolver so submit and slash cannot drift apart again.
A slash command in a ⌘T tab or split pane routes through the primary chat's dispatcher, which read the FOREGROUND view's identity for two decisions it had no business asking the foreground about. Busy: the gate read `busyRef`, a mirror of whatever chat is on screen. A brand-new tab with zero turns was told "session busy — message queued" because an unrelated chat was mid-stream, and the converse let a background send fire into a live turn. Identity: the output writer bound to the foreground's stored session, so a tab's transcript writes re-keyed its cache entry onto the primary's stored id and its queued payload landed on the primary's queue — the kickoff would then drain into the wrong conversation. `submitText` also dropped an explicit target when it routed to a slash command, running a queue drain's command against whatever was in front. Read the target session's own published state for both. One shared resolver so submit and slash cannot drift apart again.
Contributor
૮ >ﻌ< ა ci reviewran on 428c909 ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence is publishing... |
Collaborator
OutThisLife
added a commit
that referenced
this pull request
Jul 26, 2026
`/work` typed into a fresh Cmd+T tab loaded the skill in that tab and printed "⚡ loading skill: work" there, then fired the skill's kickoff prompt as a user message into whatever conversation was on screen. The dispatcher resolves its target once, through resolveTargetSessionId, and every other consumer of that answer already honors it: the output writer binds to the target's stored id, and the busy gate reads the target's own state. The send did not — `submitPromptText(message)` passed no target at all, so submit fell back to `activeSessionIdRef`, which names the foreground chat. #71805 fixed the two sibling leaks in this same function; this is the third and the one that actually moved the user's prompt. Forward the resolved pair instead. Every target the dispatcher serves — a tile, a background queue drain, a session this very call created — was hitting the same fallback, so the fix covers the class rather than the tab case that surfaced it.
19 tasks
This was referenced Jul 28, 2026
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…-queue fix(desktop): bind a tab's slash command to its own session
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
`/work` typed into a fresh Cmd+T tab loaded the skill in that tab and printed "⚡ loading skill: work" there, then fired the skill's kickoff prompt as a user message into whatever conversation was on screen. The dispatcher resolves its target once, through resolveTargetSessionId, and every other consumer of that answer already honors it: the output writer binds to the target's stored id, and the busy gate reads the target's own state. The send did not — `submitPromptText(message)` passed no target at all, so submit fell back to `activeSessionIdRef`, which names the foreground chat. NousResearch#71805 fixed the two sibling leaks in this same function; this is the third and the one that actually moved the user's prompt. Forward the resolved pair instead. Every target the dispatcher serves — a tile, a background queue drain, a session this very call created — was hitting the same fallback, so the fix covers the class rather than the tab case that surfaced it.
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
`/work` typed into a fresh Cmd+T tab loaded the skill in that tab and printed "⚡ loading skill: work" there, then fired the skill's kickoff prompt as a user message into whatever conversation was on screen. The dispatcher resolves its target once, through resolveTargetSessionId, and every other consumer of that answer already honors it: the output writer binds to the target's stored id, and the busy gate reads the target's own state. The send did not — `submitPromptText(message)` passed no target at all, so submit fell back to `activeSessionIdRef`, which names the foreground chat. NousResearch#71805 fixed the two sibling leaks in this same function; this is the third and the one that actually moved the user's prompt. Forward the resolved pair instead. Every target the dispatcher serves — a tile, a background queue drain, a session this very call created — was hitting the same fallback, so the fix covers the class rather than the tab case that surfaced it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A slash command typed in a ⌘T tab or a split pane runs through the primary chat's dispatcher, which read the foreground view's identity for two decisions it had no business asking the foreground about. Both are fixed here by reading the target session's own published state.
Busy
The gate at
slash.tsreadbusyRef— a mirror of$busy, which tracks whatever chat is on screen. A brand-new tab (openNewSessionTile('center', { listed: false }): fresh backend session, zero turns) reportedbecause an unrelated chat happened to be mid-stream. The converse leaked too: a background or tile send could fire into a live turn whenever the foreground was idle.
submit.tshad the same guard against the same flag.Identity
withSlashOutputbound its output writer toselectedStoredSessionIdRef— again the foreground. Passing that stored id intoupdateSessionStatemakesensureSessionStatetreat it as a stored-id rotation, so a tab's transcript writes re-key its cache entry onto the primary's stored session and remapruntimeIdByStoredSessionIdwith it. The queue key derived from the same value, so a queued kickoff parked on the primary's queue anduseBackgroundQueueDrainwould later fire it into the wrong conversation.submitTextalso droppedoptions.sessionIdwhen it routed to a slash command, so a background queue drain's explicit target was silently swapped for the foreground session.Shape
isTargetSessionBusyinutils.tsreads the target's published$sessionStatesentry and falls back to the foreground flag only when the target has no state yet — a just-minted session whose first publish hasn't landed. Submit and slash both call it, so they can't drift apart again.Verification
apps/desktop: 132 tests inuse-prompt-actionspass, 567 acrosssrc/app/session/hooks,src/app/chat, and the composer queue.tsc --noEmitclean. Three new tests cover the idle-target, busy-target, and tile-binding cases; the tile-binding one fails on the parent commit and passes here.Prior art
Distinct from #44978 / #45030, which are about a genuinely busy session having no escape hatch — this is a session that isn't busy at all being told it is. No open PR touches these files.