A skill argument string can be referenced four ways ($ARGUMENTS for the whole, $ARGUMENTS[N] or $N for positional, named placeholders via the arguments frontmatter list); inline !`command` runs in your shell before Claude sees the prompt, except in user/project/plugin/additional-directory skills when disableSkillShellExecution is set.
The four ways to access arguments
Each example below is a complete SKILL.md file you can drop in ~/.claude/skills/<name>/SKILL.md.
$ARGUMENTS is the whole argument string, as typed:
---
description: Fix a GitHub issue with our standards in mind.
---
Fix GitHub issue $ARGUMENTS following our coding standards.
/fix-issue 42 high-priority substitutes 42 high-priority for $ARGUMENTS.
$ARGUMENTS[N] (or shorthand $N) for positional access, 0-based, shell-quoted:
---
description: Migrate a component from one framework to another.
---
Migrate the $0 component from $1 to $2.
/migrate-component SearchBar React Vue substitutes SearchBar, React, and Vue. /migrate-component "search bar" React Vue substitutes search bar (one quoted argument) for $0.
Named arguments declared in frontmatter map to positions in declaration order:
---
description: Fix an issue and switch to its branch.
arguments: [issue, branch]
---
Fix issue $issue on branch $branch.
/fix-and-branch 42 hotfix-auth makes $issue expand to 42 and $branch to hotfix-auth.
argument-hint is purely cosmetic; it shows up during autocomplete:
---
description: Fix an issue and switch to its branch.
argument-hint: "<issue-id> <branch-name>"
arguments: [issue, branch]
---
Fix issue $issue on branch $branch.
What happens when args do not match
The skill body has $ARGUMENTS and the user passed nothing. $ARGUMENTS expands to empty. The body now reads “Fix GitHub issue following our coding standards.”, which Claude usually figures out from context but can also misread as a malformed prompt. Defend the case in the body: “If $ARGUMENTS is empty, ask the user which issue to fix.”
The skill body uses $ARGUMENTS and the user passed extra text. All of it lands in the substitution. A multi-line argument is fine; quoting is irrelevant for the whole-string placeholder.
The skill body has no $ARGUMENTS and the user passed text anyway. Claude Code appends ARGUMENTS: <your input> to the end of the skill content automatically. The argument is not lost; it is just unstructured. Useful when you want a body that mostly hard-codes the prompt and only occasionally takes an override.
The skill body uses $0/$1 and the user passed fewer arguments. Missing positions expand to empty. The same defensive prompt applies: tell Claude what to do with empty placeholders (“if $1 is missing, default to main”).
Inline shell expansion
Backtick-bang (!`command`) runs the command in your shell before the skill body is sent to Claude. The output replaces the literal in the prompt. A complete SKILL.md using inline shell:
---
description: Review the current branch's diff against main.
---
Review the diff below against `main`. Flag SQL safety issues, missing tests, and side effects in conditionals.
## Diff
!`git diff main...HEAD`
The git diff runs locally; Claude sees the diff text inline. That is how skills stay grounded in live state instead of guessing.
Two scopes for shell expansion to be aware of:
- Per-skill shell choice via the
shellfrontmatter field (bashdefault,powershellon Windows whenCLAUDE_CODE_USE_POWERSHELL_TOOL=1). - Global kill switch via the
disableSkillShellExecution: truesetting. With that on, every!`command`is replaced with a “shell command execution disabled by policy” placeholder; the skill loads but loses its live data. Managed environments commonly enable this; check/statusif a known-working skill suddenly returns generic answers.
Frontmatter that affects argument flow
| Field | What it does |
|---|---|
argument-hint | Cosmetic autocomplete hint |
arguments | Declare named positional arguments ([issue, branch] → $issue, $branch) |
shell | Shell to use for ! blocks (bash default; powershell on Windows) |
disable-model-invocation | Prevents Claude from triggering the skill automatically; the user must invoke explicitly |
allowed-tools | Pre-approves tools while the skill is active so Claude does not need to ask |
Footguns
Positional access uses shell-style quoting. /my-skill hello world second is three arguments; $0 is hello, $1 is world, $2 is second. To pass "hello world" as a single argument, the user has to quote it: /my-skill "hello world" second. New users assume natural-language input; positional skills need either explicit user discipline or a wrapper that uses $ARGUMENTS and parses inside the skill body.
The $N shorthand is single-digit only. $ARGUMENTS[10] is valid bracket-index syntax for the eleventh argument; $10 is not. Use $ARGUMENTS[N] whenever the index reaches double digits, and stick to $0–$9 for the shorthand. Mixing them in one skill body works fine.
Named arguments still use positional binding. arguments: [issue, branch] does not create a flag-style API; the user still passes positional values, and the placeholder names map to positions in declaration order. There is no --issue 42 --branch hotfix shape natively. If you need flag-style, parse $ARGUMENTS yourself in the skill body.
disableSkillShellExecution: true silently kills ! injections in user, project, plugin, and additional-directory skills. Bundled skills and managed skills are exempt and keep running shell expansions. A user-level or project-level skill that was working can suddenly return generic answers because someone (or a security script) set the flag. The skill still loads; the body just lacks the live data the ! block was supposed to fetch. Check /status for the active settings sources before debugging the skill itself.
disable-model-invocation: true removes the description from context too. Normally Claude sees every skill’s description and can auto-invoke based on the user’s prompt. With disable-model-invocation: true, the description is omitted from context, the model cannot suggest the skill, and you have to type /skill-name explicitly. Useful for destructive skills (deploy, push); confusing if you wanted “Claude should know this skill exists but only run it when I ask”.
allowed-tools is pre-approval, not restriction. Listing a tool there means “do not prompt me for this while the skill is active”. It does not mean “deny all other tools”. A skill that tries a tool not in allowed-tools still works; the user just gets a permission prompt for it. If you want to actually restrict the skill’s tool surface, run it inside a subagent (context: fork) with that subagent’s tools list set explicitly.
When NOT to use arguments
- The prompt does not vary. A skill that always says “review the current diff” does not need arguments. Skip the frontmatter and let the body hard-code the prompt.
- You want a structured form (flags, types, validation). Slash command arguments are stringly-typed and shell-quoted. Validation belongs in the skill body or in a subagent that the skill calls. For genuinely structured input, build a small CLI tool and call it from a
Bash(...)line in the skill. - You want different Claude responses based on argument presence. That is conditional logic in the body (“if
$ARGUMENTSis empty, …”); it is not free. Test with both empty and full inputs to make sure the prompt holds together. - The argument is sensitive. A
/deploy "$STRIPE_LIVE_KEY"invocation puts the key into the skill body, which goes into Claude’s context, which is logged. Do not pass secrets as arguments; reference them via env vars in the!shell“ step instead.