Custom slash commands: positional $1..$n substitution drops the first argument (args shifted left, trailing vars left literal)
Summary
Custom slash commands in .claude/commands/*.md mis-render positional arguments: the
first argument is silently dropped, the remaining arguments shift left into $1..$k,
and any $n beyond the remaining count is left as literal text (a raw $3) rather
than substituted empty. $ARGUMENTS (whole-string) substitution is unaffected —
confirmed side by side in the same renders (minimal repro below).
Reproduced 6 out of 6 times, fully deterministically, across three commands, two argument
shapes (2-arg and 3-arg), two working directories, and three invocation paths:
user-typed in the desktop app, programmatic via the agent's Skill tool, and headlessclaude -p.
Environment
- CLI: 2.1.235 (Claude Code), macOS (Darwin 25.5.0)
- Also reproduces in the Claude desktop app (Code tab), version 1.46388.2
- Model: Claude Fable 5 / Haiku 4.5 (both — rendering happens before the model)
- Commands live in a project's
.claude/commands/; in some sessions they are reached via
a git-ignored .claude/commands symlink from a sibling repo (both reproduce)
Minimal repro (confirmed on CLI 2.1.235)
.claude/commands/echoargs.md:
---
description: Diagnostic - echo positional args verbatim
argument-hint: <a> <b> <c>
---
Do nothing except report: print the four lines below exactly as they were rendered
to you, inside a fenced code block, then stop.
ARGUMENTS: $ARGUMENTS
ONE: $1
TWO: $2
THREE: $3
Then, from the project root:
claude -p "/echoargs alpha beta gamma"
Actual output (identical when invoked from the desktop app):
ARGUMENTS: alpha beta gamma
ONE: beta
TWO: gamma
THREE: $3
Expected: ONE: alpha, TWO: beta, THREE: gamma.
Real-world occurrences
Production command templates using $1=repo, $2=PR/issue, $3=round
(e.g. Review PR #$2 in dean-$1. Round: $3):
| # | Date | Invocation | Path / command source | Rendered result |
|---|------|-----------|------------------------|-----------------|
| 1 | 2026-09-03 | /review server 65 2 | desktop app, user-typed; commands via symlink | "Review PR #2 in dean-65", gh pr view 2 -R …/dean-65, literal $3 |
| 2 | 2026-09-03 | /fix server 65 2 | desktop app, user-typed; via symlink | $1←65, $2←2, $3 literal |
| 3 | 2026-09-03 | /review server 65 3 | desktop app, user-typed; via symlink | "Review PR #3 in dean-65", literal $3 |
| 4 | 2026-09-04 | /impl project 5 | desktop app, user-typed; commands owned directly, no symlink | "Implement issue #$2 in dean-5": $1←5, $2 literal |
| 5 | 2026-09-04 | echoargs repro | desktop app, programmatic Skill-tool invocation | $1←2nd arg, $2←3rd, $3 literal |
| 6 | 2026-09-04 | echoargs repro | headless claude -p, CLI 2.1.235 | identical to #5 |
Diagnostics that localize the defect
- Args reach the renderer intact. In occurrence 4 the rendered prompt's own metadata
contained <command-args>project 5</command-args> while the body of the same render
had $1←5 and a literal $2. The repro shows the same: $ARGUMENTS renders the
full string correctly in the very render whose $1 is shifted.
- Not command-name token consumption. Occurrence 5 was invoked through the agent's
Skill tool, where the command name and the args string are separate parameters — the
first arg was dropped anyway. The off-by-one is in the args→$n mapping itself
(consistent with a 1-indexed $n lookup into a 0-indexed split, or equivalent), not in
parsing the typed command line.
- Invocation-path independent — user-typed, programmatic, and headless
-pall shift
identically, so the defect is in the shared render path.
- Not symlink-, cwd-, command-, arity-, or model-related (table above).
- Secondary issue: an out-of-range
$nrenders as literal$nrather than empty.
Impact / workaround
The shift produces confidently wrong values ("dean-65", "dean-5" — nonexistent repos),
which misleads the agent worse than a blank would; three sessions recovered only via
ad-hoc gh cross-checks. Workaround adopted project-wide: templates no longer use$1..$n at all — they echo Args as typed: $ARGUMENTS, use named placeholders, and
verify the resolved target against gh before acting.