String Substitutions
Skills become dramatically more useful when they can accept input from the user and access runtime information. Most agent platforms provide a string substitution system that replaces special variables in your skill content with actual values at invocation time. This turns static instruction documents into dynamic, parameterized workflows.
How Substitution Works
When the agent loads a skill (Tier 2), it scans both the frontmatter and the body for substitution variables. These variables are replaced with their actual values before the agent sees the content. The agent never sees the raw $ARGUMENTS placeholder â it sees the actual text the user passed in.
$ARGUMENTS â The Full Argument String
The $ARGUMENTS variable contains everything the user typed after the skill name, as a single string. This is the most common substitution variable and works well when your skill expects free-form input.
---
description: Explain a concept in simple terms
---
The user wants you to explain the following topic:
$ARGUMENTS
Explain it as if teaching a junior developer. Use analogies,
avoid jargon, and include a concrete code example.When invoked as /explain dependency injection, the agent sees the body with $ARGUMENTS replaced by "dependency injection".
$1 through $9 â Positional Arguments
When your skill expects structured input with distinct parameters, use positional variables. The agent splits the argument string on whitespace and assigns each token to $1, $2, through $9.
---
description: Rename a component across the codebase
---
Rename the React component from **$1** to **$2** across the entire codebase.
## Steps
1. Find all files that import or reference `$1`
2. Update the component name in its definition file
3. Update all import statements
4. Update all JSX usage sites
5. Rename the file from `$1.tsx` to `$2.tsx`
6. Run `npm run typecheck` to verify nothing brokePositional arguments are split on whitespace. If a user types /skill my file name.txt output, then $1 is "my", $2 is "file", and so on. There is no quoting mechanism. If your skill needs a multi-word argument, use $ARGUMENTS and parse it in the instructions, or design the skill so that each argument is a single token (like a file path without spaces).
Built-in Variables
Beyond user-provided arguments, some platforms provide built-in variables that give skills access to runtime context. In Claude Code, these use the ${VARIABLE_NAME} syntax with curly braces.
${CLAUDE_SESSION_ID}
A unique identifier for the current agent session. This is useful for skills that need to create temporary files or directories without naming collisions.
---
description: Run benchmarks and save results
allowed-tools:
- Bash(*)
- Write
---
Run the project benchmarks and save results to a unique file:
```
benchmark-results-${CLAUDE_SESSION_ID}.json
```
This ensures each session's results don't overwrite previous runs.${SKILL_DIR}
The absolute filesystem path to the directory containing the current skill's SKILL.md. This is essential for referencing supporting files that live alongside the skill.
---
description: Generate API documentation from OpenAPI spec
allowed-tools:
- Read
- Write
---
Read the documentation template from:
${SKILL_DIR}/templates/api-doc.md.hbs
Read the styling config from:
${SKILL_DIR}/config/doc-style.json
Use these to generate documentation for the API endpoint
described in $ARGUMENTS.The ${SKILL_DIR} variable always resolves to an absolute path, regardless of where the agent was launched from. This makes skills portable â they work the same whether invoked from the project root or a subdirectory.
Substitution in Frontmatter
Variables work in frontmatter fields too, not just the body. This is particularly powerful with allowed-tools, where you can dynamically scope tool permissions based on user input.
---
description: Run tests for a specific module
allowed-tools:
- Bash(npm test -- --filter $1)
- Bash(npm run lint -- $1)
---
Run the test suite for the **$1** module.
1. First run the linter: `npm run lint -- $1`
2. Then run tests: `npm test -- --filter $1`
3. Report any failures with suggested fixes.Invoking /test-module auth makes the allowed-tools expand to Bash(npm test -- --filter auth) and Bash(npm run lint -- auth). This tightly scopes the skill's permissions to only the relevant module.
Handling Empty Arguments
Users will not always provide arguments. A well-designed skill should handle this gracefully. If $ARGUMENTS is empty, the variable is replaced with an empty string. You can account for this in your instructions:
---
description: Review code in a specific file or the whole project
---
Review the code for quality issues.
If a file path was provided, focus on that file: $ARGUMENTS
If no file was specified, scan the entire project for the most
critical issues and report the top 5.Write your skill instructions so that the agent does something reasonable when no arguments are provided. The agent is smart enough to interpret "If a file path was provided, focus on that file: " (with nothing after the colon) as meaning no file was specified.
Quick Reference
# Substitution Variables
## User-provided
$ARGUMENTS # Full argument string after skill name
$1, $2, ... $9 # Positional arguments (whitespace-split)
## Built-in
${CLAUDE_SESSION_ID} # Unique session identifier
${SKILL_DIR} # Absolute path to the skill's directory
## Where they work
- â Frontmatter values (description, allowed-tools, etc.)
- â Skill body (Markdown instructions)
- â Frontmatter keys (the field names themselves)What's Next
String substitutions inject static values at load time. The next lesson covers dynamic context â how to use the !`command` syntax to run shell commands and inject their live output into skill content.