Skill loading: $1-$19 in a skill file are replaced with unrelated conversation text (CLAUDE.md is unaffected)
Summary
When a skill is loaded via the Skill tool, literal $ + one-or-two-digit sequences in the skill's Markdown ($5, $19, …) do not reach the model as written. They are replaced with unrelated fragments of text from the current conversation.
The skill file on disk is unmodified. The corruption happens between the file and the model, and it is silent — nothing in the transcript indicates that the instructions were rewritten.
Observed behaviour
Values in roughly the $1–$19 range are substituted; $20 and above arrive intact. Measured in one session against the file on disk:
(Surrounding wording generalised; the tokens and their substitutions are verbatim.)
| In the skill file | What the model received |
|---|---|
| … $13,961 … | … <a phrase from the user's message>.,961 … |
| … the nearest $5 mark … | … the nearest found mark … |
| … $19.99 … $25 … | … <a sentence fragment>.99 … $25 … |
| … a $10 item … | … a the item … |
| … $3–$20 … | … fan.–$20 … |
| … ~$15 or less … | … ~the or less … |
Observed substituted: $3 $5 $10 $13 $15 $19.
Observed intact: $20 $25 $49.99 $125 $175.
Note the third row in particular: $19 was replaced while $25 on the same line survived.
The replacement text is not random. It is recognisably drawn from the surrounding conversation — in one case the exact phrase the user had typed in their message moments earlier.
The part that localises it
The same project has a CLAUDE.md and a skill file that both contain $10.
CLAUDE.md, auto-loaded into the system prompt:$10arrived intact.- The skill file, loaded via the Skill tool:
$10arrived asthe.
Same token, same machine, same session, different loading path. This points at the Skill tool's content-loading path specifically, not at general context assembly.
Also ruled out on the user's side: the file on disk is byte-clean UTF-8 with no control characters, git status clean, no shadowing copy of the skill in ~/.claude/skills, no plugins installed, and the only configured hooks (PreToolUse/PostToolUse on Bash|PowerShell|Read|Grep) never rewrite content and do not match the Skill tool.
Minimal reproduction
- Create
.claude/skills/repro/SKILL.md:
```markdown
---
name: repro
description: Reproduce dollar-token substitution.
---
# Repro
Rule A: round up to the next $5 mark.
Rule B: the threshold is $19.99.
Rule C: the cap is $25.
Rule D: the total was $13,961.
```
- Start a session, type a sentence or two of ordinary prose, then invoke the skill.
- Compare the skill text the model reports receiving against the file on disk.
Expected: all four rules arrive verbatim.
Actual: Rules A, B and D have their amounts replaced with conversation fragments. Rule C is unaffected.
Hypothesis (not verified)
The shape is consistent with skill content being used as the replacement argument of a regex replace, where $nn is interpreted as a capture-group backreference rather than literal text — e.g. JavaScript's String.prototype.replace, in which $1–$99 in the replacement string expand to captured groups. The $19 / $20 boundary would then reflect the number of capture groups in whatever pattern is being matched.
If that is the mechanism, the fix is to escape $ in the replacement ($$) or to use a replacer function, which receives the replacement as a literal string.
Impact
- Silent corruption of instructions. The model follows rewritten instructions and has no signal that anything was altered. In the session where this was found, an entire pricing rule became unrecoverable: the operative step rendered as an unrelated English word, so the rule could not be reconstructed from the text at all. Skills that encode thresholds, limits, prices or version numbers in this range are affected wherever those appear.
- Conversation content is being interpolated into instruction text. Independent of the corruption, text from the session is ending up inside what the model reads as its instructions. Flagging that as a design concern rather than describing any specific exploitation — happy to take this part to a security contact instead if that is preferred.
A user-side workaround exists (write the amounts as USD 5 so they do not match), but it requires knowing the bug exists, and the failure is invisible by default.
Environment
- Windows 11 (10.0.26200)
- Project-level skill in
.claude/skills/<name>/SKILL.md, invoked via the Skill tool - Version: could not be captured —
claudewas not on the PATH of the shell available in this session. Happy to supply it on request.
How this was found
Noticed only because the substituted text happened to be a distinctive phrase the user had typed in the same conversation, which made it obvious that the instructions were not what the file said. With less distinctive replacement text (the, found) it would have read as a typo in the skill file and gone uninvestigated.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗