fix(cli): queue /plan, /interview, /review mid-turn instead of interrupting - #1256
fix(cli): queue /plan, /interview, /review mid-turn instead of interrupting#1256kavish-19 wants to merge 2 commits into
Conversation
…upting
/plan <text>, /interview <text>, and /review <text> (and their input-mode
counterparts when submitted without inline args) called sendMessage()
directly with no check for whether a run was already in progress. Firing
one of these while a previous message was still streaming registered a new
active-run owner, which force-stops the in-flight run ('user-interrupt')
instead of queuing behind it -- so the current job was interrupted and lost
rather than queued, matching what CodebuffAI#1211 describes.
/skill:<name> already gets this right via dispatchSkillPrompt, which checks
isStreaming/streamMessageIdRef/isChainInProgressRef and falls back to
addToQueue when busy. Extract that logic into a shared sendOrQueuePrompt()
helper and route all six call sites (three in command-registry.ts, three in
router.ts) through it so they can't drift out of sync with the busy check
again.
Added a failing-first regression test covering both entry paths (input mode
and inline slash-command args) for all three commands, confirmed red
against the unfixed code, green after.
Claude-Session: https://claude.ai/code/session_018vPhyqaaoKa8cgs7GEnyq5
|
Please codebuff-team merge this PR, i need this fix, and thank you @kavish-19 for tha patch |
|
Good bug fix. The diagnosis is correct: The test additions in One thing worth double-checking before porting: Small, well-scoped, tested, and addresses a real filed complaint (#1211). This is a solid first PR. |
Review feedback on CodebuffAI#1256 asked whether plan/interview/review needed to capture pending attachments. Checking it turned up two real defects, one of them introduced by that PR. prepareUserMessage resolves attachments as `attachments ?? useChatStore.getState().pendingAttachments`, so passing an explicit array suppresses the store fallback and passing no key at all uses it. sendOrQueuePrompt got that backwards on both branches: - The queue branch defaulted to `[]`, so a mid-turn /plan, /interview or /review queued with no attachments and left the staged ones in the store, where they attached to whatever the user sent next. Before CodebuffAI#1256 these paths called sendMessage with no attachments key and picked them up via the fallback, so this was a regression, not a pre-existing gap. - dispatchSkillPrompt passed capturePendingAttachments() as an argument, which evaluates before the busy check. An idle /skill:<name> therefore cleared the store and then sent without the captured value, dropping the attachments outright. Capture inside the queue branch instead, where the skill path already had it, and drop the parameter so neither call site can reintroduce the split. Both defects are covered by tests that fail against the previous commit. Claude-Session: https://claude.ai/code/session_018vPhyqaaoKa8cgs7GEnyq5
|
Good catch on the attachments question — I checked it and it turned up two real defects, one of them introduced by this PR. Pushed a fix in dffc4bf. The mechanism is in const allAttachments = attachments ?? useChatStore.getState().pendingAttachmentsPassing an explicit array suppresses the
Both are fixed by capturing inside the queue branch, where the skill path already had it, and dropping the parameter so neither call site can reintroduce the split. Verification: both new tests confirmed red against 0413433 before the fix, green after ( |
What
/plan <text>,/interview <text>, and/review <text>— and their input-mode counterparts when submitted without inline args — calledsendMessage()directly with no check for whether a run was already in progress.Why this is a bug
Every other mid-turn submit path in this file (plain text via the composer,
/skill:<name>) checksisStreaming || streamMessageIdRef.current || isChainInProgressRef.currentand falls back toaddToQueue()when busy, so a message typed while the agent is still working waits its turn./plan,/interview, and/reviewnever got that treatment. Firing one of them while a previous message is still streaming callssendMessage()immediately, which registers a new active-run owner inregisterActiveRun— and that force-stops the in-flight run ('user-interrupt') to make room for the new one. The current job is interrupted and lost instead of being queued behind it. Related: #1211, where a user reports a follow-up message "overwriting" the job in progress instead of queuing.Repro (no race required — deterministic):
/plan add dark mode(or/interview ...,/review ...) and submit.Fix
dispatchSkillPrompt(used by/skill:<name>) already implements the correct pattern. Extracted its busy-check-then-queue-else-send logic into a sharedsendOrQueuePrompt()helper incommand-registry.ts, and routed all six call sites through it:command-registry.ts: the/interview,/plan,/reviewcommand handlers (inline-args form)router.ts: theplan,interview,reviewinput-mode submit handlersdispatchSkillPromptitself is now a thin wrapper oversendOrQueuePrompt, so there's one place that owns "send now vs. queue" for every prompt-dispatching command going forward.Testing
Added regression tests in
router-steering.test.tscovering both entry paths (input mode and inline slash-command args) for all three commands, mid-turn and idle:sendMessagewas called, run interrupted) by temporarily reverting the source changes and re-running.Also ran the full
cli/src/commands/suite (198/199 pass; the one pre-existing failure — an OSC 52 clipboard test — reproduces identically on unmodifiedmainand is untouched by this change) andtsc --noEmiton theclipackage (no new errors; the only typecheck errors present are pre-existing environment issues — missing@types/react-domand thetarpackage types — unrelated to the files this PR touches).