Skill body corrupted by positional-argument substitution: $N tokens replaced by caller arguments (breaks bundled claude-api pricing table)

Resolved 💬 0 comments Opened Jul 11, 2026 by flytochange Closed Jul 13, 2026

Skill bodies are corrupted by positional-argument substitution: caller arguments are spliced into $N tokens

Claude Code 2.1.207, macOS (darwin 25.5.0), model Opus 4.8.

Summary

When a skill is invoked with arguments, every $ followed by digits in the skill body is treated as a shell-style positional parameter and expanded:

  • $0, $1, $2 (indices matching a supplied argument) are replaced by the caller's words.
  • Numeric tokens with no matching index ($99, $100, $B$6) are deleted entirely, numeral included.
  • The file on disk is untouched, so the corruption is invisible to any check that reads the file. It exists only in the body as delivered to the model.

This destroys the model pricing table in Anthropic's own bundled claude-api skill, and it lets caller-supplied text be injected into any skill's body.

Impact: the bundled claude-api pricing table

On disk (claude-api/SKILL.md:186-195):

| Model             | Model ID            | Context        | Input $/1M | Output $/1M |
| Claude Fable 5    | `claude-fable-5`      | 1M             | $10.00     | $50.00      |
| Claude Opus 4.8   | `claude-opus-4-8`   | 1M             | $5.00      | $25.00      |
| Claude Sonnet 5   | `claude-sonnet-5`   | 1M             | $3.00 ($2.00 intro through 2026-08-31) | $15.00 ($10.00 intro) |
| Claude Haiku 4.5  | `claude-haiku-4-5`  | 200K           | $1.00      | $5.00       |

Invoked as claude-api with arguments alpha beta gamma, delivered to the model:

| Model             | Model ID            | Context        | Input $/1M | Output $/1M |
| Claude Fable 5    | `claude-fable-5`      | 1M             | .00     | .00      |
| Claude Opus 4.8   | `claude-opus-4-8`   | 1M             | .00      | .00      |
| Claude Sonnet 5   | `claude-sonnet-5`   | 1M             | .00 (gamma.00 intro through 2026-08-31) | .00 (.00 intro) |
| Claude Haiku 4.5  | `claude-haiku-4-5`  | 200K           | beta.00      | .00       |

Every price is gone. Two cells now contain the caller's argument words (beta.00, gamma.00). The table still looks like a table of numbers, so a model answering "what does Opus cost?" has no signal that anything is missing. The $/1M in the header survives, because no digit follows the $.

Minimal reproduction

Create ~/.claude/skills/repro-dollar-bug/SKILL.md:

---
name: repro-dollar-bug
description: Minimal reproduction for positional-argument substitution in skill bodies. Invoke with arguments alpha beta gamma.
---

# Repro

Line A: The jump from $1 to $0 is bigger than $2 to $1.
Line B: A price of $99 feels much cheaper than $100.
Line C: Excel absolute reference: =B5*(1+$B$6)
Line D: Shell example: if [ -z "$1" ]; then echo "no arg"; fi
Line E: Control line with no dollar sign, 20% off, should survive intact.

Invoke the skill with arguments alpha beta gamma, then compare the body as delivered against the file:

| Line | On disk | As delivered |
|---|---|---|
| A | The jump from $1 to $0 is bigger than $2 to $1. | The jump from beta to alpha is bigger than gamma to beta. |
| B | A price of $99 feels much cheaper than $100. | A price of feels much cheaper than . |
| C | =B5*(1+$B$6) | =B5*(1+$B) |
| D | if [ -z "$1" ]; then echo "no arg"; fi | if [ -z "beta" ]; then echo "no arg"; fi |
| E | Control line ... 20% off, should survive intact. | identical |

Substitution map, 0-indexed against alpha beta gamma: $0alpha, $1beta, $2gamma. Out-of-range numerics ($6, $99, $100) → empty string. $B survives, since the next character is not a digit. Digit matching is greedy: $100 is consumed whole, not parsed as $1 + 00 (which would have produced beta00).

Note when scripting the repro: the skill must exist before the session starts, or an unrelated tool call must intervene, before the registry picks it up.

Expected vs actual

Expected: the skill body reaches the model verbatim. Arguments are exposed through an explicit, documented token, and every other $ sequence is left alone.

Actual: the body is passed through positional substitution before it reaches the model.

Why this is not "working as intended"

$ARGUMENTS and $1/$2 interpolation is documented behavior for custom slash commands. It is not documented for skill bodies. A skill body is prose and reference material, not a command template, and it routinely contains literal dollar amounts, spreadsheet formulas, and shell examples. Anthropic's own bundled skills contain all three, and they break.

Why this is worse than an ordinary formatting bug

1. It fails silently and produces plausible nonsense. A pricing skill delivered without its numbers still reads like advice. Line A arrives as a grammatically well-formed but semantically false sentence built from the caller's own words. Nothing in the delivered content indicates that anything was lost.

2. It defeats verification. The file on disk is correct, so grep, lint, and human review all find nothing. In our case two agents independently reported this bug and we dismissed both as hallucinations, because reading the file appeared to prove them wrong. Only a run that compared the delivered body against the file confirmed it. A silent corruption that trains people to dismiss accurate reports is worse than a loud failure.

3. It is a content-injection vector, not only a formatting issue. Caller-supplied arguments land inside the skill body at every $1/$2/$3 site. In claude-api, arguments end up inside the official pricing table.

4. It breaks code and formulas, not just prices. Line C and Line D above. The bundled xlsx skill hits exactly this: SKILL.md:49 documents =B5*(1+$B$6) as the recommended absolute reference, and $6 is consumed, leaving =B5*(1+$B).

Affected bundled skills (verified in SKILL.md bodies, not in reference files or scripts)

  • claude-api: 8 lines in SKILL.md contain $+digit, including the entire model pricing table.
  • xlsx: SKILL.md:49, the Excel absolute-reference example.

Third-party skills that document prices are affected wholesale. In one marketing skill we use, all eight lines of its pricing section arrive without a single number.

Suggested fix

Do not run positional substitution over the skill body. If argument interpolation into skill bodies is intended, restrict it to one explicit token and leave every other $ sequence verbatim, or provide an escape mechanism and document it.

View original on GitHub ↗