Creating and optimizing Claude Code Skills including activation patterns, content structure, and development workflows...
Reference for developing effective skills.
Run a check against a skill path in $ARGUMENTS, defaulting to the skill you just edited:
--validate: run skill-lint (see Validation) for frontmatter, naming, and reference-depth validation.--structure: run the directory-structure check (${CLAUDE_SKILL_DIR}/scripts/check-structure.ts) for the SKILL.md, scripts/, references/, assets/ layout.With neither flag, use the skill as an authoring reference. See Validation.
---
name: plugin-name:skill-name
description: Third-person capability description with trigger terms
argument-hint: "[--flag] [<positional>]"
allowed-tools: [Read, Grep, Glob]
model: sonnet
effort: low
context: fork
agent: Explore
background: false
user-invocable: false
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate.sh"
once: true
---
name: Lowercase letters, numbers, hyphens only (max 64 chars). See Naming.description: Third-person, includes trigger terms and use cases (max 1024 chars).argument-hint: Arguments the skill accepts, shown in the slash menu after the skill name. See Argument Hints.allowed-tools: Tools Claude can use without permission when skill is activemodel: Override the conversation's model. Prefer a tier alias (haiku, sonnet, opus, fable) or inherit over a dated model ID.effort: Reasoning effort while the skill is active, applied only when typed as a slash command. Model invocation ignores it. Defaults to the conversation's effort. See Reasoning Effort for levels and cache cost by model.context: Set to fork to run in isolated subagent contextagent: Agent type when context: fork (Explore, Plan, general-purpose, or custom)background: Only with context: fork. false waits for the fork's result in the invoking turn instead of backgrounding it. Default true.user-invocable: Hide from slash menu when false (default: true)disable-model-invocation: Block model (Skill-tool) invocation and drop the skill's name and description from the always-on catalog (zero recurring context cost); still slash-invocable. Opposite of user-invocable: false, which hides the slash menu but keeps the description loaded for the model.hooks: Skill-scoped hooks (PreToolUse, PostToolUse, Stop)Plugin skills use plugin-name:skill-name with a colon namespace (e.g., gitlab:ci, things:url). The part after the colon should not repeat the plugin name. Skip the prefix when name equals plugin name. For standalone skills, use gerund form (verb + -ing): processing-pdfs, analyzing-data. Avoid vague names like helper, utils.
~/.claude/skills/ (personal), .claude/skills/ (project), plugins (bundled)
Load the prompting skill before writing a skill body or a reference file. It carries the rules for every document a model executes. The sections below cover what changes when that document is a Claude Code skill.
The description field is the pointer Claude scans to decide whether to activate the skill. Write it for the model and make it slightly pushy, since under-triggering is the common failure. The wording rules are in the prompting skill.
List triggers: the requests that call for the skill. End with the boundary to any sibling skill a request could land on. Tools, techniques, and capabilities go in the body.
description: Use when timing a program or command, asking whether a change or version made it faster, or building a benchmark harness. For repeated optimization toward a target, use performance:hill-climb.
The highest-signal content in any skill is a ## Gotchas section documenting failure modes hit in practice. Grow it as edge cases surface.
A skill is a folder. Keep SKILL.md a concise hub and push details into references/, scripts/, and assets/. Tell Claude what files exist and when to read them. Organize references by domain. A question about one domain then loads only that file.
A model-invocable skill's description costs tokens in every session. Keep an invoked skill's body under roughly 4k tokens, since it is re-injected in full at every compaction. A references/ file costs nothing until its pointer fires.
Split one skill into two by invocation when the branches need different frontmatter: a different model, different allowed-tools, or one branch routed by the model while the other stays user-invoked.
Skills that depend on user-specific context should check for a config.json in ${CLAUDE_SKILL_DIR} or ${CLAUDE_PLUGIN_DATA}. If missing, prompt the user for setup and store answers for future runs.
${CLAUDE_PLUGIN_DATA}Skills can maintain state across runs: append-only logs, JSON records, SQLite databases. Use ${CLAUDE_PLUGIN_DATA} for storage that survives plugin upgrades.
Include helper scripts and libraries that Claude can import and compose on the fly. Document scripts with "Run script.py" (execute) vs "See script.py" (reference).
Skill-scoped hooks activate only when the skill is invoked and last for the session. Use these for guardrails that would be annoying globally but valuable in specific contexts (e.g., blocking destructive commands during prod operations).
| Variable | Description |
|---|---|
$ARGUMENTS |
All arguments passed when invoking the skill. Appended automatically if absent. |
$ARGUMENTS[N] / $N |
Access a specific argument by 0-based index. |
${CLAUDE_SESSION_ID} |
Current session ID. |
${CLAUDE_SKILL_DIR} |
Absolute path to the skill's directory. Substituted in skill content: the body, ! injection commands, and allowed-tools. |
These substitutions apply to skill content, not the frontmatter hooks: block. The hooks engine expands only ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT}, and ${CLAUDE_PLUGIN_DATA} (hooks reference); ${CLAUDE_SKILL_DIR} there resolves to an empty string. In a hook command, reference a bundled script by plugin root instead: ${CLAUDE_PLUGIN_ROOT}/skills/<skill>/scripts/check.ts.
argument-hint declares the arguments a skill accepts. It renders in the slash menu after the skill name and reminds the user which flags exist. Give every directable skill a hint, even when it usually runs with none. A skill that branches internally ("if the user wants X") should expose that branch as a flag.
<doc-path> [--draft].[staged | <range> | HEAD].[--flag]. Value flags are [--flag value]. Enumerated values pipe-join without inner spaces: [--role author|reviewer].A skill that declares an argument-hint must parse $ARGUMENTS (or $0/$1 for positionals) and act on what it finds. Add an ## Arguments section to the body mapping each token to its behavior, with a stated default for every flag so the no-argument invocation stays well-defined.
The bang-backtick syntax runs shell commands before the skill content is sent to Claude. The output replaces the placeholder โ Claude sees only the result, not the command. This is preprocessing, not something Claude executes. Use it to inject live data (git state, CLI output, file contents) so the harness extracts and runs the commands without waiting on the model.
See references/patterns.md for syntax, examples, and gotchas.
Skills follow the Agent Skills directory convention. Only SKILL.md is required; all directories are optional.
skill-name/
โโโ SKILL.md # Required: instructions and frontmatter
โโโ scripts/ # Executable code agents can run (self-contained, explicit errors)
โโโ references/ # Documentation loaded on demand (focused, domain-named files)
โโโ assets/ # Static resources (templates, images, data files)
A PostToolUse hook validates writes to skill directories against this structure.
Reserve ALL CAPS for files with special meaning (SKILL.md, README.md). Use lowercase for all other files. Keep references one level deep.
A skill-scoped PostToolUse hook runs skill-lint automatically when SKILL.md files are edited. For manual checks, run bun run skill-lint path/to/skill/ from the project root.
Load the guide that covers the question at hand: