fix(desktop): route /branch from tile to the tile's session, not the foreground chat - #73236
fix(desktop): route /branch from tile to the tile's session, not the foreground chat#73236JonthanaHanh wants to merge 1 commit into
Conversation
…foreground chat When /branch is dispatched from a session tile (⌘T tab or split pane), the slash dispatcher forwards the tile's runtime ID as sessionHint. The branch action handler previously ignored sessionHint and called branchCurrentSession(), which reads foreground-only refs (activeSessionIdRef, $messages, busyRef, selectedStoredSessionIdRef) and always branches the primary chat. Fix: when sessionHint targets a non-foreground session, resolve the tile's stored session ID from $sessionStates and use branchStoredSession() instead. branchStoredSession reads the stored transcript directly from the backend — no active-session or live message-store dependency — so it correctly branches the tile's session. The foreground path (no sessionHint, or sessionHint === activeSessionId) is unchanged. Fixes NousResearch#73207
andyst-dev
left a comment
There was a problem hiding this comment.
Thanks for picking this up. I found two session-safety gaps in the current approach:
- When
sessionHintis explicit but$sessionStateshas no matchingstoredSessionId, the handler falls through tobranchCurrentSession()and branches the foreground chat. That preserves the exact cross-session misroute this issue is trying to eliminate; an explicit target should fail closed instead. branchStoredSession()reads the persisted transcript and does not consult the tile runtime's livebusystate/messages. During an in-flight tile turn it can therefore branch a stale transcript instead of showing the existing busy guard used by foreground branching. The issue explicitly calls out the target transcript and busy state as part of the broken contract.
There is also no behavioral regression test here—the harness only gains a stub. A safer shape would be to let branchCurrentSession accept the invoking runtime ID, select busy, messages, storedSessionId, and cwd from that runtime's cached state, and refuse to fall back when an explicit runtime is unavailable. Please add dispatcher and session-action tests covering the correct runtime/message count, both directions of busy-state isolation, and the missing-target fail-closed case.
|
A complete alternative implementation is available in #73251. It follows the runtime-targeted shape suggested in the review here: |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating the missing sessionHint handling; current main confirms the native action still calls the foreground-only helper at apps/desktop/src/app/session/hooks/use-prompt-actions/slash.ts:478-480.
Problems
- The new fallback at
apps/desktop/src/app/session/hooks/use-prompt-actions/slash.ts:439-450callsbranchCurrentSession()with no target when an explicit tile runtime has no$sessionStatesentry. That silently branches the foreground chat—the behavior this PR is intended to prevent. An explicit target must fail closed. - The new route passes only a stored id to
branchStoredSession()(slash.ts:440-444). That helper reads persisted history throughgetSessionMessagesand has no live-runtime busy check (apps/desktop/src/app/session/hooks/use-session-actions/index.ts:1267-1291), so it cannot preserve the invoking tile's live transcript or reject an in-flight tile. - The test change at
index.test.tsx:153is only a harness mock; it does not exercise/branchwith a tile target.
Suggested changes
- Carry
sessionHintinto a runtime-aware branch operation, using that runtime's cached transcript, busy state, parent id, and gateway context. Fail closed if the explicit runtime is gone. - Add regression coverage for tile targeting, a busy tile, and a missing tile runtime.
Automated hermes-sweeper review.
| } | ||
| } | ||
|
|
||
| await branchCurrentSession() |
There was a problem hiding this comment.
An explicit non-foreground sessionHint that has disappeared from $sessionStates reaches this foreground-only call. That recreates the cross-session branch bug; an explicit target must fail closed rather than silently branch the active chat.
| const storedId = states[sessionHint]?.storedSessionId | ||
|
|
||
| if (storedId) { | ||
| await branchStoredSession(storedId) |
There was a problem hiding this comment.
branchStoredSession is the sidebar/stored-session path: it re-reads persisted history and does not use this tile runtime's busy state. /branch from a live tile needs a runtime-aware branch path so an in-flight tile cannot branch stale history.
|
Closing as superseded by #73251. #73251 covers the full case this PR introduced Credit preserved: #73251 was authored on the runtime-targeted shape this PR surfaced. Thanks for the isolation work. |
Summary
Fixes #73207. When
/branchis dispatched from a session tile (⌘T tab or split pane), the slash dispatcher forwards the tile's runtime ID assessionHint. Thebranchaction handler previously ignoredsessionHintand calledbranchCurrentSession(), which reads foreground-only refs (activeSessionIdRef,$messages,busyRef,selectedStoredSessionIdRef) and always branches the primary chat — regardless of which tile the command was entered from.Root Cause
The
branchhandler atslash.ts:429was the only action handler that didn't destructure or forwardsessionHintfrom theSlashActionCtx. All other session-targeting handlers (yolo,handoff,skin) already extract and use it:Fix
When
sessionHinttargets a non-foreground session, resolve the tile's stored session ID from$sessionStatesand usebranchStoredSession()instead.branchStoredSessionreads the stored transcript directly from the backend — no active-session or live message-store dependency — so it correctly branches the tile's session.The foreground path (no
sessionHint, orsessionHint === activeSessionIdRef.current) is unchanged.Changes
apps/desktop/src/app/session/hooks/use-prompt-actions/slash.tsbranchStoredSessiontoSlashCommandDeps; updatebranchhandler to checksessionHintapps/desktop/src/app/session/hooks/use-prompt-actions/index.tsbranchStoredSessiontoPromptActionsOptions; forward touseSlashCommandapps/desktop/src/app/contrib/wiring.tsxbranchStoredSessionfromuseSessionActionstousePromptActionsapps/desktop/src/app/session/hooks/use-prompt-actions/index.test.tsxbranchStoredSessionto test harnessTest Plan