Skip to content

fix(cli): queue /plan, /interview, /review mid-turn instead of interrupting - #1256

Open
kavish-19 wants to merge 1 commit into
CodebuffAI:mainfrom
kavish-19:fix/queue-plan-interview-review-mid-turn
Open

fix(cli): queue /plan, /interview, /review mid-turn instead of interrupting#1256
kavish-19 wants to merge 1 commit into
CodebuffAI:mainfrom
kavish-19:fix/queue-plan-interview-review-mid-turn

Conversation

@kavish-19

@kavish-19 kavish-19 commented Sep 3, 2026

Copy link
Copy Markdown

What

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

Why this is a bug

Every other mid-turn submit path in this file (plain text via the composer, /skill:<name>) checks isStreaming || streamMessageIdRef.current || isChainInProgressRef.current and falls back to addToQueue() when busy, so a message typed while the agent is still working waits its turn.

/plan, /interview, and /review never got that treatment. Firing one of them while a previous message is still streaming calls sendMessage() immediately, which registers a new active-run owner in registerActiveRun — 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):

  1. Send any message; while it's still streaming,
  2. type /plan add dark mode (or /interview ..., /review ...) and submit.
  3. Expected: queued behind the current run.
  4. Actual: the current run is interrupted; the in-progress job is lost.

Fix

dispatchSkillPrompt (used by /skill:<name>) already implements the correct pattern. Extracted its busy-check-then-queue-else-send logic into a shared sendOrQueuePrompt() helper in command-registry.ts, and routed all six call sites through it:

  • command-registry.ts: the /interview, /plan, /review command handlers (inline-args form)
  • router.ts: the plan, interview, review input-mode submit handlers

dispatchSkillPrompt itself is now a thin wrapper over sendOrQueuePrompt, 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.ts covering both entry paths (input mode and inline slash-command args) for all three commands, mid-turn and idle:

  • Confirmed red against the unfixed code (sendMessage was called, run interrupted) by temporarily reverting the source changes and re-running.
  • Green after the fix.
bun test cli/src/commands/__tests__/router-steering.test.ts
 13 pass / 0 fail

Also ran the full cli/src/commands/ suite (198/199 pass; the one pre-existing failure — an OSC 52 clipboard test — reproduces identically on unmodified main and is untouched by this change) and tsc --noEmit on the cli package (no new errors; the only typecheck errors present are pre-existing environment issues — missing @types/react-dom and the tar package types — unrelated to the files this PR touches).

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant