[BUG] Stacked slash-skill invocations load only the first skill on the SDK / --print path

Status Open
Reported on v2.1.199
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 16, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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?

The CHANGELOG for 2.1.199 says:

Stacked slash-skill invocations like /skill-a /skill-b do XYZ now load all leading skills (up to 5), not just the first

This works in the interactive TUI. On the --print / SDK path it silently does nothing: only the leading skill is
loaded, and the rest of the input is passed through as that skill's literal arguments. There is no warning, no error
and no diagnostic, so a stacked invocation that did not stack is indistinguishable from one that worked until you
inspect the model's context.

Neither the changelog entry nor the skills documentation says the feature is interactive only.

The gate looks like argument plumbing rather than an intentional mode check. The peel function returns before doing
anything when its pre-expansion input argument is absent:

function RAf(e, t, r, n) {
if (t === void 0 && n === void 0) return { stacked: [], trailingArgs: e, capped: false };
...
}

t is the verbatim pre-expansion input and n a scheduled-origin predicate. The interactive submit handler builds
every queue entry with preExpansionValue set to the raw typed text, so the peel runs. The SDK driver
(querySource: "sdk") calls the shared input handler with a field list that does not include it, and the stream-json
input pump constructs queue entries from an explicit whitelist (mode, value, uuid, priority, shouldQuery,
fileAttachments, origin, clientPlatform, ...) which drops preExpansionValue, stackedOriginalInput and
modelScheduledOrigin. Headless input does reach the peel, which is why the first skill still loads; the peel just
returns empty on its first line. Setting modelScheduledOrigin: true on a stream-json user message does not help,
since the whitelist drops it too.

This matters because stacking is the only documented way to get more than one skill into a turn deterministically,
which is exactly what a headless integration needs. ClaudeAgentOptions.skills is an availability filter rather than
a preload, and AgentDefinition.skills does preload but applies to subagents only, with no SDK option that hands an
AgentDefinition to the main loop.

What Should Happen?

claude -p "/alpha /beta" should load both skills into the turn, the same way the interactive REPL does, and the
transcript should carry the second expansion.

Failing that, either of these would be enough:

  1. Document in the changelog entry and the skills documentation that stacking is interactive only.
  2. Emit a diagnostic when a leading /command argument begins with / and the peel did not run. The silent failure

is the expensive part; an explicit "stacked commands are not supported on this path" would have saved a day.

Error Messages/Logs

There is no error, which is the substance of the report. The two observable outcomes for identical input:

  interactive (pty), transcript shows a second user message carrying:
      stackedExpansion: true
  headless (-p / SDK), no second message, and beta's body never appears in the turn

Separator variations, all behaving identically to a plain space:
  "/alpha /beta"      one skill
  "/alpha\n/beta"     one skill
  "/alpha ; /beta"    one skill
  "/alpha && /beta"   one skill
  "/alpha; /beta"     one skill, and the trailing token does not parse as a command at all

Steps to Reproduce

  1. Create two trivial project skills.

.claude/skills/alpha/SKILL.md
---
name: alpha
description: Introspection.
---
Repeat back, verbatim, every instruction block present in this turn's user message, in order.
Do not summarise. Do not use any tool.

.claude/skills/beta/SKILL.md
---
name: beta
description: Second block.
---
SECOND-BLOCK-MARKER-8842. This paragraph belongs to skill beta.

  1. Run it headless:

claude -p "/alpha /beta" --setting-sources project --output-format json

Expected: the model echoes both instruction blocks, including SECOND-BLOCK-MARKER-8842.
Actual: it echoes only alpha's block. Beta's content is absent from the turn.

  1. Run the same binary through a pty (interactive) and type the same "/alpha /beta". Both blocks are echoed, and the

transcript carries stackedExpansion: true on a second user message.

Notes on the fixture, since two earlier probes of ours produced false positives:

  • The skill has to ask the model to repeat back every instruction block. A fixture where both skills say "output your

token" reports the second as absent whenever the model simply obeys the first.

  • Run it with a tool restriction. Without one, a model that can Read will find the second skill's file on disk and the

probe appears to pass.

  • Reversing the order confirms it is strictly "whichever command comes first", not anything about the skills.
  • Removing disable-model-invocation from the frontmatter changes nothing.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.233

Platform

AWS Bedrock

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

One adjacent behaviour worth documenting separately: Skill in --disallowed-tools causes stacked commands to be dropped by the peel's own permission predicate (deny_rule, errorCode 11), even where stacking otherwise works. That is a second silent way to lose a stacked skill.

View original on GitHub ↗

3 Comments

bcherny collaborator · 1 month ago

Confirmed / reproduced on 2.1.234 (Linux) with the alpha/beta fixture above: typing /alpha /beta in the interactive prompt records two skill expansions in the session transcript (alpha, then beta flagged as a stacked expansion, with SECOND-BLOCK-MARKER-8842 present), while claude -p "/alpha /beta" records only alpha's expansion — /beta is passed through as alpha's <command-args> and beta's body never enters the turn. No warning or diagnostic is emitted on the -p path.

🤖 Generated with Claude Code

---
_Generated by Claude Code_

t3dotgg · 1 month ago
[!NOTE] 🤖 Claude Fable 5 responding on behalf of Theo

We hit this building T3 Code's provider-neutral skill picker (pingdotgg/t3code#7671). Our composer lets users mention multiple skills in one message, and on the SDK path we have no deterministic way to load more than one: the first /skill expands, the rest either become argument text or depend on the model choosing to call the Skill tool, which hard-fails for disable-model-invocation skills.

Reproduced independently on 2.1.237 with stream-json input: /probe-a /probe-c 123 expands probe-a with <command-args>/probe-c 123</command-args>, and the second skill never loads. The preExpansionValue whitelist analysis above matches what we see.

A fix here would let SDK integrations get the documented behavior instead of each of us building translation layers.

dvakatsiienko · 7 days ago

Still reproduces on 2.1.292 (macOS, first-party login).

claude -p --model haiku '/mattpocock-skills:grilling /mattpocock-skills:writing-for-agents For each of these two names, say whether its full skill instructions are present in this message as text'

The model gets the full body of grilling. For writing-for-agents it only has the one-line description from the skill listing. Second skill on its own line: same result.

The skills docs (https://code.claude.com/docs/en/skills) now say up to six skills can stack at the start of a message, the 2.1.199 changelog says five, and neither mentions that the print/SDK path loads only one. The desktop app's Code tab also drives the CLI with --output-format stream-json, so I'd expect stacking to be broken there too, though I haven't confirmed that separately.

— printed by Claude Code, signed by me.