Skill tool: args parameter corrupts shell positional parameters ($1/$2) in SKILL.md code examples
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When the Skill tool is invoked with an args parameter, and the target SKILL.md file contains a fenced code block with shell positional parameters ($1, $2, etc., e.g. bash -c '. "$1" && some_func "$2"' _ "<path>" "<value>"), the positional-parameter tokens inside that code block get corrupted in the content that is actually surfaced to the model. The SKILL.md file on disk is unmodified and correct — the corruption only appears in what the tool_result / injected skill body contains.
I observed this twice, in two separate sessions, always with the issue-create-gate skill (which has a bash -c '. "$1" && write_ticket "$2"' _ "<lib_path>" "<user 選択値>" example at SKILL.md line 82), and always when the Skill call included a non-empty args string whose content followed a "タイトル: type(scope): keyword ..." shape (a Japanese-language convention for "Title: <conventional-commit-style prefix>: <first keyword>"):
Occurrence 1 (this session): Skill({skill: "issue-create-gate", args: "タイトル: chore(tests): assert_stdout_* Group B(...)の統合検討\n\n本文趣旨:\n..."})
→ surfaced code block became: bash -c '. ":" && write_ticket "assert_stdout_*"' _ "$HOME/.../issue-tracker-state.sh" "<user 選択値>"
(expected: bash -c '. "$1" && write_ticket "$2"' _ ...)
Occurrence 2 (earlier session, same repo/skill): Skill({skill: "issue-create-gate", args: "タイトル: chore(hooks): commit-verification-gate Phase 2 の代替設計案...\n\n背景: ..."})
→ surfaced code block became: bash -c '. ":" && write_ticket "commit-verification-gate"' _ ...
In both cases:
"$1"was replaced with":"(a bare colon)"$2"was replaced with the first "keyword" token that follows thetype(scope):portion of theargsstring (i.e., whatever comes right after the second colon in the args text)
This is a consistent, reproducible pattern across two independent occurrences with different args content, which rules out a one-off rendering glitch. I verified:
- The
SKILL.mdfile on disk (both in~/.claude/skills/and the source repo it's synced from) contains the correct"$1"/"$2"form and was not modified between sessions (unchanged mtime). - A sibling
Skillcall in the same session with noargsparameter (related-issue-search) rendered the same style ofbash -c '. "$1" && ...'code block correctly, unmodified. - This is not a transcript-parsing artifact on my end — I inspected the raw JSONL bytes of the tool_result content directly and confirmed the corrupted string is what was actually recorded as having been surfaced to the model.
Executing the corrupted command as literally shown fails immediately (_: line 1: :: No such file or directory), which is how I noticed it. If a skill's code example were less obviously broken when corrupted (e.g. if the substituted value happened to be a valid path), this could silently execute the wrong command instead of failing loudly.
What Should Happen?
When a Skill tool call includes an args parameter, the skill's SKILL.md body content (including any fenced code blocks) should be surfaced to the model byte-for-byte identical to its on-disk content, regardless of the args value. Shell positional parameters ($1, $2, ...) inside example code blocks are literal instructional text for the model to copy — they should never be treated as template placeholders to be filled in from the args string.
Reproduction
- Create (or use an existing) skill whose
SKILL.mdcontains a code block like:
``bash``
bash -c '. "$1" && some_func "$2"' _ "/some/path" "<placeholder>"
- Invoke it via the
Skilltool with a non-emptyargsstring that has the shape"Title: type(scope): keyword ...more text..."(e.g.args: "タイトル: chore(tests): assert_stdout_* ..."or an English equivalent like"Title: fix(parser): normalize-whitespace some more context"). - Inspect the tool_result / injected skill body text returned by the
Skillcall. - Expected: the code block is unchanged, still containing
"$1"and"$2"literally.
Observed: "$1" becomes ":" and "$2" becomes the token immediately following the second : in the args string.
Environment
- Claude Code CLI, Skill tool (skills loaded from
~/.claude/skills/) - Reproduced across two separate sessions on macOS