[FEATURE] Execute queued built-in slash commands (e.g. /compact) at end of turn instead of delivering them to the model as text
Environment
- Claude Code version: 2.1.201
- Client: VS Code extension (also reproducible in CLI)
- OS: Windows 11
Current behavior
Typing a built-in slash command (e.g. /compact) while the agent is still working queues it as a plain user message. When the turn ends, the model receives it as literal text (The user sent a new message while you were working: /compact) and cannot execute it — compaction never happens. Verified empirically on 2.1.201: the context meter kept growing and the command arrived in the model's context as a regular message.
Notably, custom skills / slash commands queue and run just fine (they are executed by the model when the queued message is delivered). Only built-ins (/compact, /cost, /permissions, /clear, …) silently lose their command character when queued. #1077 reported this as a bug back in 0.2.108; it was closed by the stale bot without a fix.
Requested feature
When a queued message consists of a built-in slash command, hold it and execute it client-side once the agent becomes idle (in order with the rest of the queue), instead of delivering it to the model as text. Purely local commands (like /cost, cf. #1077) could even execute immediately.
Use case: fire-and-forget session hand-off across many parallel sessions
Power users run a dozen+ parallel sessions and rely on a hand-off chain:
/handover— a custom skill that writes a detailed session-state file to disk,/compact— compact the conversation,/session-resume— a custom skill that reads the state file back after compaction.
Steps 1 and 3 queue perfectly today. Step 2 breaks the chain: the user must sit and watch the session, wait for the handover turn to finish, and only then type /compact at idle. With queued built-in execution, the whole chain could be typed in one go — walk away, and the session compacts and re-anchors itself unattended.
Workarounds we evaluated and rejected: keyboard-automation robots (unusable with many concurrent sessions — no way to target the right chat input), claude --resume from a second process (forks the session instead of compacting the open one), auto-compact (threshold not configurable, fires far too late — see #72062/#70681/#75335).
Related
- #1077 — same underlying behavior, reported as a bug, closed stale
- #71803 — agent-invokable compaction (complementary: that one lets the model trigger it; this request needs no model involvement, just deferred client-side execution of queued built-ins)
---
🤖 Filed with Claude Code
3 Comments
+1 — hitting the exact same thing, and it's a real friction point.
Same behavior with settings commands like
/configand/model: typed while a turn is running, they queue as plain messages and only take effect after the turn ends. Worse, some (e.g. the thinking toggle set via/config) don't apply to the running session at all — you have to restart. Since these are purely client-side/settings commands, having them wait on the model turn is confusing and easy to miss.For comparison, in Codex CLI I can change settings mid-session without waiting for the current response to finish — it never blocks on the model turn. Coming from that, this Claude Code behavior feels like a step back, especially with long turns or many parallel sessions.
Strong support for the deferred client-side execution proposed here. At minimum, purely local/settings commands (
/config,/model,/cost) should execute immediately (or in queue order at idle) instead of being swallowed as text.Mac OS, claude code 2.1.214
Still present on 2.1.247, but the behavior is not one behavior — I measured three different
outcomes for the same mid-turn
/compact, depending on delivery context.Worth correcting one assumption in this thread: the CLI does already implement deferred
client-side execution. It just doesn't hold in every case.
All runs below used the exact argv the VS Code extension spawns
(
--output-format stream-json --verbose --input-format stream-json), a scripted stdin, andclaude-haiku-4-5-20251001, on Linux, 2.1.247.Case 1 — no tool loop: works correctly
Sent
Write exactly 150 words about ..., then/compact400ms later, while the turn was stillstreaming. The command was held and executed after the turn ended:
compact_resultis the honest failure of a two-message conversation, not the bug. The point isthat it ran as a command, at idle, with no model involvement. This is exactly the feature
requested in this issue, and it already exists on this path.
Case 2 — during a tool loop: silently dropped
Same channel, same version. Turn = six Bash calls (
echo/sleep, 6 turns, ~40s). Reproducedfour times, varying everything I could think of that might matter:
| # | Variation | Outcome |
|---|---|---|
| 2 |
/compactwritten ~5s into the turn | dropped || 2b | fired on the first
tool_result, so the loop was provably running | dropped || 4 | same, plus
origin:{kind:"human"}on the envelope (as the extension sends) | dropped || 5 | same, plus a second content block (
<ide_opened_file>context, as IDE clients attach) | dropped |Every time: no
status: compacting, nocompact_result, and no text delivery either. Thecommand vanishes — nothing anywhere in the stream acknowledges it, and the turn completes
normally. That matches \#85697.
Case 3 — VS Code extension, deep in an agentic tool loop: delivered to the model as text
Observed live in the extension chat, twice. I could not reproduce it from scripted stdin (see
the four variations above), so something about the extension's session differs and I have not
identified what. The wrapper text itself is definitely CLI-owned, not client-owned: it appears in
the
claudebinary (stringsfindsThe user sent a new message while you were working:\n) andin neither extension bundle.
/compactclicked while the agent was several tool calls into a turnarrived inside the running turn as:
This is the worst of the three, and worse than a silent no-op, because the wrapper *instructs the
model to address it*. The model duly acknowledges it in prose — "you want to compact, I'll finish
this task first and come back to it" — so the command fails and burns turn output reasoning
about an operation it has no way to perform. To the user it reads as the agent deliberately
refusing a client-side operation.
What this suggests
The deferral logic exists but is bypassed once the turn is an agentic tool loop, at which point
queued input is routed into the steering path (injected as mid-turn user text) or lost. So this
issue is arguably not "implement deferred execution" but "make the existing deferral cover
tool-loop turns, and stop routing built-in slash commands into steering injection at all".
Cross-refs from this same behavior: \#85697 (silent drop), \#88491 (
/clearnot cancelling whatis queued behind it), \#86609 (queued
/compactflushed by an interrupt runs with no indicator),\#87037 (the ordering rules are undocumented — which is why all of this is invisible to users).
A client-side hold works as a stopgap
For anyone blocked on this in the VS Code extension, holding the command in the client — never
letting it onto the channel mid-turn — sidesteps all three cases. In the extension's session
layer that is four small edits: intercept in
send()when the text matches/^\/(compact|clear|model)(\s|$)/and the session is busy (stash it, show an inline "queued"note, return without sending); flush where the
resultmessage clearsbusy; replay through thereal
send()with a bypass flag so it reaches the CLI at idle and expands normally, re-queueingif a new turn has already started; and clear the queue on
interrupt(), since Esc means "no",not "compact now".
I patched my local 2.1.247 bundle this way:
node --checkclean, 27 assertions against stubs ofthe patched code all passing (idle passthrough, queue-and-hide while busy, flush on turn end,
re-queue if a new turn starts first, interrupt clears, last-queued-wins, and near-miss names like
/modelsand/clearcachecorrectly ignored). Not proposing that patch for merge — the shippedbundle is minified — and I have not exercised it in a live window yet, so treat the design as
verified and the integration as unverified.
---
🤖 Drafted with Claude Code
We hit this regularly in the Claude Desktop app (Code tab), Claude Code 2.1.296, macOS.
Workflow: we finish work with a custom slash command
/upd(it only updates the project's Markdown files and memory notes, then reports), and then want to run/compact. If/compactis typed while/updis still running, it reaches the model as plain text ("The user sent a new message while you were working: /compact") and the built-in command is never executed. The model cannot run/compactitself, so the compaction is silently lost, and you only notice later when the context keeps growing.Expected: built-in commands typed during a running turn are queued and executed after the turn ends, as the CLI docs describe ("If you send a command while Claude is responding, Claude Code queues it and runs it after the current turn finishes"). The changelog for 2.1.275 fixes this for VS Code only; the Desktop app still shows the old behaviour for us.
It would help a lot if this worked in the Desktop app as well, or if a custom command could chain a built-in
/compactat its end.