[BUG] Stacked slash-skill invocations load only the first skill on the SDK / --print path
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 andmodelScheduledOrigin. 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 anAgentDefinition 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:
- Document in the changelog entry and the skills documentation that stacking is interactive only.
- Emit a diagnostic when a leading
/commandargument 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
- 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.
- 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.
- 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-invocationfrom 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.
3 Comments
Confirmed / reproduced on 2.1.234 (Linux) with the alpha/beta fixture above: typing
/alpha /betain the interactive prompt records two skill expansions in the session transcript (alpha, then beta flagged as a stacked expansion, withSECOND-BLOCK-MARKER-8842present), whileclaude -p "/alpha /beta"records only alpha's expansion —/betais passed through as alpha's<command-args>and beta's body never enters the turn. No warning or diagnostic is emitted on the-ppath.🤖 Generated with Claude Code
---
_Generated by Claude Code_
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
/skillexpands, the rest either become argument text or depend on the model choosing to call the Skill tool, which hard-fails fordisable-model-invocationskills.Reproduced independently on 2.1.237 with stream-json input:
/probe-a /probe-c 123expandsprobe-awith<command-args>/probe-c 123</command-args>, and the second skill never loads. ThepreExpansionValuewhitelist 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.
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.