[FEATURE] Execute queued built-in slash commands (e.g. /compact) at end of turn instead of delivering them to the model as text

Status Open
Reported on v2.1.201
Maintainer reply None cached
Activity 3 comments · opened Jul 10, 2026

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:

  1. /handover — a custom skill that writes a detailed session-state file to disk,
  2. /compact — compact the conversation,
  3. /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

View original on GitHub ↗

3 Comments

PromotezCitizen · 2 months ago

+1 — hitting the exact same thing, and it's a real friction point.

Same behavior with settings commands like /config and /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

janwan2003 · 1 month ago

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, and
claude-haiku-4-5-20251001, on Linux, 2.1.247.

Case 1 — no tool loop: works correctly

Sent Write exactly 150 words about ..., then /compact 400ms later, while the turn was still
streaming. The command was held and executed after the turn ended:

<<< result/success success turns=1
<<< system/status {"status":"compacting"}
<<< system/status {"status":null,"compact_result":"failed",
                   "compact_error":"Not enough messages to compact."}

compact_result is the honest failure of a two-message conversation, not the bug. The point is
that 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). Reproduced
four times
, varying everything I could think of that might matter:

| # | Variation | Outcome |
|---|---|---|
| 2 | /compact written ~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, no compact_result, and no text delivery either. The
command 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 claude binary (strings finds The user sent a new message while you were working:\n) and
in neither extension bundle. /compact clicked while the agent was several tool calls into a turn
arrived inside the running turn as:

The user sent a new message while you were working: /compact This is how Claude Code surfaces messages the user sends mid-turn — within the running turn, often alongside the next tool result. Address the message above as you continue this turn.

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 (/clear not cancelling what
is queued behind it), \#86609 (queued /compact flushed 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 result message clears busy; replay through the
real send() with a bypass flag so it reaches the CLI at idle and expands normally, re-queueing
if 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 --check clean, 27 assertions against stubs of
the 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
/models and /clearcache correctly ignored). Not proposing that patch for merge — the shipped
bundle 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

mandrael · 11 hours ago

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 /compact is typed while /upd is 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 /compact itself, 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 /compact at its end.