Anatomy of a skill
You have built an agent that reads, writes, runs commands, asks permission, spawns subagents, talks to MCP servers, and fires hooks. Every one of those is code you wrote. A skill is the escape hatch from that: a way to teach the agent a new procedure without touching its source — you drop a file on disk and the agent picks it up.
A skill is a single file: SKILL.md. It has two halves — YAML frontmatter (metadata) and a Markdown body (instructions). The cleverness is in when each half is loaded. The frontmatter's description is cheap and always in context so the model knows the skill exists; the body is expensive and loads only when the skill is actually invoked. That split is the entire reason skills scale: you can have a hundred of them and pay almost nothing until you call one.
---
name: commit
description: Stage and commit the current changes. Use when the user asks to commit.
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
disable-model-invocation: true
---
Stage the relevant files, write a conventional-commit message
summarizing the diff, and commit. Do not push.
The frontmatter is the part the agent reads at startup. Each field tunes one dimension of the skill:
name— the display label in skill listings. (It does not set the command you type — that comes from where the file lives. More on that next step.)description— what the skill does and when to use it. This is the one field that matters: it is loaded into context for every session so the model can decide whether to reach for the skill. Put the key use case first; Claude Code truncates the listing at 1,536 characters.allowed-tools— tools the skill may use without prompting you for permission while it is active (a space- or comma-separated string, or a YAML list). It grants, it does not restrict — your permission settings still govern everything else.disable-model-invocation— settrueto make the skill user-only: you can still type/commit, but the model can never trigger it on its own. Use it for anything with side effects you want to time yourself — deploys, commits, sending messages.
Everything after the closing --- is the body — the instructions the model follows once the skill runs. This is where the procedure lives: the checklist, the steps, the gotchas. It can be long, because it costs nothing until invoked. The body can also reference dynamic values ($ARGUMENTS, session/skill-dir variables) that get filled in at invoke time — you will implement that expansion in step 03.
The load split is the whole design:
- Description → loaded at startup. Cheap. Always present so the model knows the skill is available.
- Body → loaded on invoke. Expensive. Pulled in only when the skill is actually called, by
/nameor by the model deciding thedescriptionmatches.
This is the sentence to remember: the description is always in context; the body is not. In a normal session every skill's description is loaded so the model knows what is available, but the full content only loads when the skill is invoked. That is why a skill body can hold a thousand lines of reference material and still cost you nothing on a session where you never call it — the opposite of stuffing the same text into CLAUDE.md, which is paid for on every turn.
One exception worth noting: a skill with disable-model-invocation: true keeps its description out of context entirely (the model can't invoke it, so it doesn't need to know it exists) — the body still loads when you invoke it.
Skills vs. legacy slash commands
Before skills, custom slash commands were a single Markdown file: .claude/commands/deploy.md created /deploy. Custom commands have been merged into skills. Both of these create /deploy and run the same way:
- Legacy command —
.claude/commands/deploy.md(one file, the filename is the command name). - Skill —
.claude/skills/deploy/SKILL.md(a directory nameddeploy, withSKILL.mdinside; the directory name is the command name).
Existing .claude/commands/ files keep working and support the same frontmatter. The skill form adds three things the flat file can't: a directory to hold supporting files, frontmatter to control who invokes it (disable-model-invocation), and the ability for the model to load it automatically when the description matches. If a skill and a command share a name, the skill wins.
.claude/
skills/
deploy/
SKILL.md → /deploy (directory name is the command)
helper.py (supporting file, only the skill dir can hold these)
commands/
review.md → /review (legacy: filename is the command)
~/.claude/skills/... user-level skills, available across all projects