Skill/command argument substitution is off by one and limited to $0/$1

Status Open
Reported on v2.1.259
Maintainer reply None cached
Activity 2 comments · opened Sep 6, 2026

Summary

When a skill or command is invoked with arguments, the loader substitutes $0 and $1 in the injected body with args[0] and args[1] — 0-indexed. Per the documented convention $1 should be the first argument, so placeholders are off by one. $2–$9 are never substituted at all.

Two consequences:

  1. Commands read the wrong argument. $1 yields argument 2; $2 yields nothing and is passed through as the literal text $2.
  2. Literal $0/$1 text in prose is silently corrupted. Any $ followed by 0 or 1 and then a non-digit — dollar amounts like $1.42B, $1M, $0, $1,000,000 — is replaced by a word from the user's prompt. No error; the number just becomes a plausible-looking token.

(2) is the more serious half: it is silent, and it corrupts documentation content that has nothing to do with parameters.

Environment

  • Claude Code 2.1.259, desktop app (Code tab), model Opus 5
  • Windows 11 Enterprise 10.0.26100
  • Plugin skills loaded from ~/.claude/plugins/cache/

Expected vs actual

Skill invoked with arguments ALPHA BRAVO CHARLIE:

| Placeholder | Expected | Actual |
|---|---|---|
| $1 | ALPHA | BRAVO |
| $2 | BRAVO | $2 (literal) |
| $3 | CHARLIE | $3 (literal) |
| $0 | (unsubstituted) | ALPHA |

Reproduction

A skill body containing:

index0=$0 index1=$1 index2=$2 index5=$5 amount=$1.42B literal=$500k

invoked with ALPHA BRAVO CHARLIE yields:

index0=ALPHA index1=BRAVO index2=$2 index5=$5 amount=BRAVO.42B literal=$500k

Two things to note. $1.42B → BRAVO.42B: substitution does not require $N to be a standalone token, so it reaches into ordinary prose. $500k survives only because the character after $5 is another digit.

(Placeholder names and amounts throughout this report; the behaviour was observed across five invocations of a real plugin on 2.1.259.)

Observed pattern

| Body contains | Args | Received |
|---|---|---|
| ~$1.42B | ALPHA BRAVO CHARLIE | ~BRAVO.42B |
| adjustment = $0 | same | adjustment = ALPHA |
| = "Less than $1" | same | = "Less than BRAVO" |
| ~$500k/month (~$6M/yr) | same | unchanged |
| LIMIT_$2M, ±$5K | same | unchanged |
| script.py $1 $2 | AAA BBB | script.py BBB $2 |

The last row is a user-visible failure. A command that shells out to a wrapped script passed the second argument where the script expected the first, plus a literal $2, and the script errored out on the wrong input. The plugin markdown was written correctly against the documented convention.

Related observation

When the injected body contains no $0/$1, the loader appends a trailing ARGUMENTS: <raw string> line instead. When substitution does occur, no trailer is appended. Self-consistent, but it means a body mixing both — a real $1 parameter plus a literal dollar amount — silently consumes the arguments and corrupts the prose.

Impact

Any plugin following the documented $1/$2 convention. That includes the first-party databricks plugin in anthropics/claude-plugins-official, where commands/doctor.md:21 and commands/setup.md:21 both use $1 (static read of the shipped files; not invoked to confirm).

For content corruption the exposure is any $ followed by 0 or 1 then a non-digit. In one plugin audited for this, that was 16 occurrences across 6 skill files, including monetary targets and two literal values that mirror strings stored in a source system (e.g. a column whose stored value is the string Less than $1) and so cannot be reworded without making the documentation wrong.

Workaround

Authors can dodge the content corruption by writing USD 1.42B instead of $1.42B. For parameters, $ARGUMENTS plus manual parsing is the usual advice, though per #46922 inline $ARGUMENTS substitution appears to have been dropped — in practice the appended ARGUMENTS: trailer still makes the raw string visible to the model.

Neither workaround helps where the $1 is a literal value in a source system; those need an escape mechanism.

Suggested fix

  1. Index positional parameters from 1, so $1 is the first argument.
  2. Support $1–$9, not just $0/$1.
  3. Provide an escape (\$1 or $$1) for literal dollar amounts and stored string values.
  4. Consider restricting substitution to standalone tokens, so $1.42B in prose is not a substitution site.

Possibly related

  • #23585 — forked-skill $1/$2 not resolving at all (closed as completed). Different failure: here they resolve, but to the wrong index.
  • #46922 — $ARGUMENTS substitution in SKILL.md (closed as not planned).

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗