Build1 publisher2 min readPublished Updated
Claude Code 2.1.278 keeps quotes in $ARGUMENTS and strips them from positional arguments
Claude Code 2.1.278 kept quotes in a skill's $ARGUMENTS, stripped them from $0 and left an unfilled $2 as literal text across 12 test runs. The tester checked each result against the session transcript, the only record of what the model was actually sent.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- The tests used throwaway echo skills in a scratch directory made with mktemp, so no CLAUDE.md file or existing skill could leak into the runs.
- Adding the argument-hint field to a skill changed nothing in the text the skill body received after substitution.
- A bare $HOME or $ARGUMENTS typed as an argument dropped out of the positional slots but still appeared inside $ARGUMENTS.
- On the test date, the documentation's slash-commands page and skills page were byte-identical, with the same MD5 hash and the same title.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A skill body that uses both $ARGUMENTS and $0 is working with two quoting conventions at once, so a command built from the raw string will contain quotation marks the positionals never show.
- decision Asking the model what it received is a weak way to debug a skill; diffing the transcript's substituted body against the SKILL.md is the reliable check.
- constraint These substitution rules are established for Claude Code 2.1.278 under claude -p only, so a team that depends on them has to rerun an echo skill after upgrading before relying on them.
The test skill is one line of instructions. Its body tells the model to reply with `ARGS=[$ARGUMENTS] A0=[$0] A1=[$1] A2=[$2] AB1=[$ARGUMENTS[1]]` and nothing else [2]. Called as `/echo-args "quoted words" x`, it came back as `ARGS=["quoted words" x] A0=[quoted words] A1=[x] A2=[$2] AB1=[x]` [2]. A1 and AB1 both held `x`, so `$1` and `$ARGUMENTS[1]` resolved to the same slot [2].
That line is still the model's reply. The author wanted to "see the substituted text with my own eyes rather than trust either the docs or the model's paraphrase" [15]. "But a model reply is a model reply," the author wrote [16]. Claude Code writes each session to `~/.claude/projects/<escaped-cwd>/<session-id>.jsonl`, where the escaped path is the working directory with its slashes turned into hyphens [11]. Under `-p`, a skill call appears there as two user messages [12]. The first carries the invocation and its raw `command-args`. The second carries the substituted body, prefixed with a "Base directory for this skill:" line that Claude Code adds itself [12].
Reading that second message is the part of the method I would copy. In 11 of the 12 runs the model's reply matched the substituted line character for character. Run 9 was the exception [13]. One miss in 12 is about 8%, from skills built to do nothing but echo [18][9]. The author called the exception "a good reason to read transcripts rather than replies" [17]. The available text of the post does not describe how the run 9 reply differed.
Where the documentation states a rule, the runs follow it. The skills page says positional access uses `$ARGUMENTS[N]` or the shorter `$N`, counted from zero, so `$0` is the first argument [4]. It also says: "Indexed arguments use shell-style quoting, so wrap multi-word values in quotes to pass them as a single argument." [3] The test call behaved exactly that way, with two quoted words landing in `$0` as one value [2].
I think the unfilled slot is where skill authors get caught. With two arguments supplied, `$0` and `$1` fill and `$2` has nothing to take [4]. It stays in the body as literal text [7]. A body that says "compare against $2" then reaches the model with the placeholder intact, and the model has to guess what an unexpanded variable means.
The record covers 12 runs by one author on Claude Code 2.1.278, all on 2026-09-22 [5]. The whole setup is a SKILL.md with one echo line and a `claude -p` call using `--output-format json` and `--permission-mode default` [2].
What to watch
- A Claude Code release after 2.1.278 that blanks unfilled positionals or changes how $ARGUMENTS handles quotes.
- A revision of the skills documentation that addresses unfilled slots, bare $ tokens as arguments, or what argument-hint does to substitution.
- An account of how the run 9 reply differed from the substituted line Claude Code sent.