Skills
A skill is a directory containing a SKILL.md file whose description Claude Code loads at startup and whose body loads only when you or Claude invoke it.
What it is
A skill is a folder with SKILL.md at its root. The file has YAML frontmatter and a markdown body. The directory name becomes the command you type: .claude/skills/deploy-staging/SKILL.md gives you /deploy-staging. Alongside SKILL.md you can ship templates, reference documents, and executable scripts.
Skills load in two stages, which is what makes them cheap. At startup Claude Code puts only each skill’s one-line description into context so Claude knows what exists. The body loads when the skill actually runs. A 400-line reference document costs almost nothing until the moment it is needed, unlike CLAUDE.md content, which is paid for on every request.
Custom commands have been merged into skills. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and support the same frontmatter. Existing .claude/commands/ files keep working; skills add the supporting-file directory, invocation control, and automatic loading. Claude Code skills follow the open Agent Skills standard, with Claude Code extensions on top.
What it does for you
- It gives a repeatable procedure one definition. The release checklist you paste into chat every Thursday becomes
/release, and when a step changes you edit one file instead of remembering the new version. - It keeps long reference material out of the context window until it is relevant, which is the difference between a 400-line API convention document being free and being a per-request tax.
- It lets you decide who may trigger a procedure.
disable-model-invocation: truemeans Claude can never run your deploy on its own;user-invocable: falsemeans a background-knowledge skill never clutters the/menu.
How it works
01Claude Code discovers skill directories at startup
It reads
.claude/skills/in your working directory and every parent up to the repository root,~/.claude/skills/,.claude/skills/inside any--add-dirdirectory, pluginskills/directories, and the managed-settings location.02Only descriptions enter the context window
Each skill contributes its
description, pluswhen_to_useif set. The combined text is truncated at 1,536 characters in the listing, so the key use case goes first. A skill withdisable-model-invocation: trueis left out of this listing entirely.03The description decides when Claude loads the skill
Claude matches your prompt against those descriptions. This is why the description is the trigger surface: "Deploys to production. Use when the user asks to ship, release, or deploy" fires reliably, while "Deployment helper" does not.
04Invocation injects the body as a user message
Whether you type
/nameor Claude decides the skill is relevant, the body is appended to the conversation. Nothing earlier changes, so the prompt cache stays intact. Once loaded, the body stays in context for the rest of the session.05Bash injection and substitutions run before Claude sees the body
A `
!commandline is executed and replaced by its output.$ARGUMENTS,$0,${CLAUDE_SKILL_DIR},${CLAUDE_PROJECT_DIR}, and${CLAUDE_SESSION_ID}` are substituted at the same point.06Supporting files load only when referenced
Claude reads
reference.mdor runsscripts/validate.shwith its ordinary tools when the body tells it to. KeepSKILL.mdunder 500 lines and push detail into those files. Claude Code watches the skill directories that existed at session start, so an edit toSKILL.mdis picked up within the current session without a restart, and/reload-skillspicks up a brand-new directory.
How to implement it
01Create the directory
Run
mkdir -p .claude/skills/summarize-changesfor a project skill, or use~/.claude/skills/for one that follows you across every project.02Write SKILL.md with a trigger-shaped description
The description is what Claude matches against. Name the task and the phrases that should fire it, and put the primary use case in the first sentence.
03Pull live state into the prompt if the skill needs it
Add a `
!git diff HEAD` line so the instructions arrive with the real diff inlined instead of asking Claude to go find it.04Decide who can invoke it
Add
disable-model-invocation: trueto anything with side effects, such as deploys, commits, or outbound messages. Adduser-invocable: falseto pure background knowledge.05Test both invocation paths
Type
/skill-nameto confirm the body runs, then phrase a request that matches the description and confirm Claude loads it on its own. Edits are picked up live, without a restart.
Examples
---
name: release-notes
description: Writes the changelog entry for a release from the merged pull requests since the last tag. Use when the user asks for release notes, a changelog entry, or "what shipped".
argument-hint: [version]
arguments: version
disable-model-invocation: true
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/merged-prs.sh *) Read Edit
---
## Commits since the last tag
!`git log $(git describe --tags --abbrev=0)..HEAD --oneline`
## Merged pull requests
!`${CLAUDE_SKILL_DIR}/scripts/merged-prs.sh`
## Instructions
Write the entry for version $version into `CHANGELOG.md`, above the previous
release. Follow the format in [reference.md](reference.md) exactly.
Group changes under **Added**, **Changed**, **Fixed**, and **Removed**. Drop
anything that is not user-visible: dependency bumps, CI changes, and internal
refactors do not appear. Each line names the change, not the commit, and ends
with the pull request number in parentheses.
If there are no user-visible changes, say so and write nothing to the file..claude/skills/release-notes/
├── SKILL.md # required: frontmatter + instructions
├── reference.md # the changelog format, read only when needed
└── scripts/
└── merged-prs.sh # executed by the !`...` line, never loaded as textUse it when
- A release or deploy checklist you run on a schedule and want triggered only by hand.
- Codebase conventions too long for CLAUDE.md, such as a full API design guide that matters twice a month.
- A review procedure that must be applied the same way every time, bundled with the rubric it scores against.
- A migration recipe that ships with the codemod script it tells Claude to run.
- Background knowledge about a legacy subsystem, marked
user-invocable: falseso it informs Claude without appearing in the/menu.
Avoid it when
- The fact is short and needed in every session. A build command belongs in CLAUDE.md; wrapping it in a skill means Claude has to decide to load it first.
- The rule must be enforced. A skill is instructions Claude may or may not follow; a hook is a shell command Claude Code runs regardless.
- The description would be so broad it fires on unrelated prompts. An over-broad trigger costs you the skill body on turns that did not need it, and it crowds out context you did need.
- You need the work done in a separate context window with its own tool restrictions. That is a subagent, though a skill can delegate to one with
context: fork.
Common mistakes
SYMPTOMClaude never loads the skill automatically, even on prompts that obviously match.
FIXRewrite the
descriptionas a trigger, not a title. Name the task and the phrases that should fire it, and confirmdisable-model-invocationis not set.SYMPTOMA skill fires on unrelated prompts and its body sits in context for the rest of the session.
FIXNarrow the description to the specific task, or add
paths:frontmatter so it only activates when Claude is working on matching files.SYMPTOMClaude deploys, commits, or posts something because the skill looked applicable.
FIXAdd
disable-model-invocation: true. Claude Code then blocks the call and tells Claude not to reproduce the steps another way, so it suggests you run/deployyourself.SYMPTOMA skill’s bundled script prompts for permission on every run.
FIXAdd
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/run.sh *)and reference the script through the same${CLAUDE_SKILL_DIR}variable in the body, so the rule matches the exact command.SYMPTOMUploading the skill to claude.ai fails with
Unexpected key(s) in SKILL.md frontmatter.FIXOutside Claude Code only
name,description,license,compatibility,metadata, andallowed-toolsare accepted. Strip the Claude Code-only fields from any skill you package for upload.
Best practices
- Write the description as a trigger sentence with the key use case first — it is the only part loaded at startup.
- Keep
SKILL.mdunder 500 lines and move detail into sibling files the body links to. - Set
disable-model-invocation: trueon anything with side effects you want to time yourself. - Use `
!command` to inline live state instead of telling Claude to go fetch it. - Reference bundled files with
${CLAUDE_SKILL_DIR}so the skill works from any working directory. - Put the instructions that matter most at the top: after compaction, skill bodies are re-injected truncated to 5,000 tokens each and 25,000 total.
Try it in five minutes
Build a skill that summarises your uncommitted changes, and watch progressive disclosure happen.
- 1.Run
mkdir -p ~/.claude/skills/summarize-changesand createSKILL.mdinside it. - 2.Give it a description like
Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed or wants a commit message.and a body containing a `!git diff HEAD` line plus instructions. - 3.Open a git repository, edit a file, start
claude, and run/context— only the one-line description is loaded. - 4.Type
/summarize-changesand confirm the diff was inlined before Claude read it. - 5.Run
/clear, then ask "what did I change?" in plain English and confirm Claude loads the skill on its own.
Related concepts
Verified against code.claude.com/docs/en/skills on 2026-08-09. See content/SOURCES.md for the full table.
← / → MOVE BETWEEN CONCEPTS