Custom slash commands: positional $1..$n substitution drops the first argument (args shifted left, trailing vars left literal)

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Sep 4, 2026

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 headless
claude -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

  1. 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.

  1. 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.

  1. Invocation-path independent — user-typed, programmatic, and headless -p all shift

identically, so the defect is in the shared render path.

  1. Not symlink-, cwd-, command-, arity-, or model-related (table above).
  2. Secondary issue: an out-of-range $n renders as literal $n rather 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.

View original on GitHub ↗