[BUG] ↑ to edit a queued message discards its input mode — a queued `!` shell command silently comes back as a prompt (pop-to-edit returns no `mode`; both callers hardcode "prompt")
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
A message queued while Claude is working keeps its input mode (bash for a !-prefixed shell command, prompt otherwise), and the queue preview under the prompt renders it as a shell command, so it looks correct while queued.
Pressing <kbd>↑</kbd> to pull it back for editing discards that mode. The queued text returns to the input buffer in prompt mode with no !, so pressing Enter sends it to the model as an ordinary prompt instead of executing it as a shell command.
The ! cannot be recovered from the text, because it was never in the text. Typing ! switches the input mode and is consumed, so the queue entry is { value: "echo hello", mode: "bash" }. Once mode is dropped, nothing distinguishes it from a prompt.
The same keystroke handles this correctly everywhere else: <kbd>↑</kbd> through command history stores a mode per entry, re-detects it, strips the !, and restores the mode — it even filters history by mode. Only the queue path drops it.
Two secondary effects of the same code path, mentioned because they share a fix and a repro rather than as separate reports:
- The pop is all-or-nothing (observed, see below). Every editable queued item is merged into one buffer joined by newlines, so two queued messages become a single two-line prompt — and since the buffer carries one mode, a queue mixing
bashandpromptentries cannot round-trip both even in principle. - The pop is destructive and immediate — the items leave the queue the moment <kbd>↑</kbd> is pressed, so a user who presses <kbd>↑</kbd> to look at the queue has already dequeued it.
What Should Happen?
Pulling a queued message back for editing should restore the input mode it was queued with, exactly as <kbd>↑</kbd>-through-history already does — a queued !echo hello should return to the input in shell mode (or equivalently with the ! re-prepended), and pressing Enter should run it as a shell command.
Error Messages/Logs
None — the failure is entirely silent. No error, no warning; the queued command
simply arrives at the model as a prompt instead of executing.
Steps to Reproduce
- Start
claudein any directory. - Give it something slow enough to leave a turn in flight, e.g.
read every file in this repo and summarize it. - While it is still working, type
!echo helloand press Enter. The message is queued, and the queue preview under the prompt shows it as a shell command. - Press <kbd>↑</kbd> (input empty, cursor on the first line).
- Observed: the input buffer now contains
echo hellowith no!, and the prompt is in normal prompt mode — not shell mode. - Press Enter. Observed: the text is sent to the model as a prompt. Expected:
echo helloexecutes as a shell command.
Direct contrast, same keystroke, in the same session — this is the clearest demonstration:
- With no turn in flight, run
!echo hellonormally so it executes and enters history. - Press <kbd>↑</kbd>.
- The input comes back in shell mode, with the
!intact.
Same key, same input buffer, opposite handling — history restores the mode, the queue discards it.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
N/A — see Additional Information; the behaviour has been present for as long as a pop-to-edit path has existed.
Claude Code Version
2.1.233 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Confirmed empirically on 2.1.233, driving a real session and reading the pane. Claude Code renders the input prompt as ! in shell mode and ❯ in prompt mode, so the discriminator needs no interpretation:
queued, before ↑ : ! echo hello <- queue preview, shown as a shell command
! Press up to edit queued messages
after ↑ : ❯ echo hello <- shell mode gone, no `!`
same session, same key, through history instead of the queue:
after ↑ : ─── History 1/1 ───
! echo hello <- shell mode restored
A second run queued two messages and pressed ↑ once, confirming the pop takes the whole editable queue rather than the most recent entry:
queued : ! echo first
! second plain prompt
after ONE ↑ : ❯ echo first
second plain prompt
Both returned together under a single ❯, joined by a newline. Note both queued entries rendered with !: input mode persists across a queued submit, so the second message was queued in bash mode too, and what came back is two shell commands merged into one two-line prompt. (A queue holding a genuine mix of bash and prompt entries was therefore not exercised — for that case the merge is lossy in a further way, since one buffer can only carry one mode.)
Where it is in the shipped binary. Symbols below are visible in the distributed 2.1.233 artifact (strings over ~/.local/share/claude/versions/2.1.233); build commit f8d57569aaf350fe25dc4dfa10cad59db8ea4d45.
The queue manager exports two pop-to-edit functions, popAllEditable and popEditableAt. Both return an object carrying text, cursorOffset and images — and no mode:
// popAllEditable — merges every editable entry, returns three fields
return n.length = 0, n.push(...We), c(), { text: Tt, cursorOffset: ft, images: et }
// popEditableAt — single entry, same three fields
return { text: et, cursorOffset: it, images: Qt }
Both callers in the prompt-input component then set the mode to a literal:
// pop-all path
let Sr = DPn(N, Fe, S); if (!Sr) return !1;
Tt(Sr.text), m("prompt"), st(Sr.cursorOffset) // m = setMode
// per-item path (queueEditIndex)
let uo = Eep(Sr, N, Fe, S); ...
Tt(uo.text), m("prompt"), st(uo.cursorOffset)
For contrast, the history hook in the same component threads the mode through instead of hardcoding it — U(value, mode, pastedContents, pos), whose callback runs m(mode), with the ! stripped only after the mode has been captured.
Suggested fix. Carry the queued entry's mode out of popAllEditable / popEditableAt and pass it to the mode setter instead of the "prompt" literal. For the pop-all path, where entries may have differing modes, the merge is already lossy — restoring the mode when all popped entries share one, and otherwise re-prepending ! to the bash-mode lines as text, would both be improvements over discarding it.
Precedent in this exact function. 2.1.72 shipped "Fixed queued messages not showing attached images, and images being lost when pressing ↑ to edit a queued message." images was added to the pop return value at that point; mode was not added alongside it, and is the remaining dropped field.
History. Bisected across 30 releases by counting anchor strings in each published build. Queueing arrives in 0.2.75; ↑-to-edit is named as the escape hatch in the 2.1.69 changelog ("Use Up arrow to pull queued messages back for editing"); popAllEditable becomes a distinct symbol at 2.1.143; per-item pop (popEditableAt, queueEditIndex, popOne) lands at 2.1.172, gated behind CLAUDE_CODE_KB_COHESION_FIXES and off by default. Enabling that flag gives per-item selection but does not fix this — that path hardcodes m("prompt") too. The mode loss is present in every version examined that has a pop-to-edit path, so this is not a regression.
Related but distinct (both closed, both different code paths, listed to save triage time):
- #61286 — shell-mode code-block suggestions dropping the
!prefix. Same symptom class, different source: rendered suggestions, not the queue. - #60834 — draft lost when <kbd>↑</kbd> navigates history. Concerns draft preservation in the history path, not mode preservation in the queue path.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗