How to Write a Claude Code Skill (SKILL.md), From Zero
A skill loads on demand instead of paying context rent in every session — that is the whole point. Here is the anatomy, a real example, and the three mistakes that make a well-written skill never get used.
A skill is a folder with a SKILL.md in it — a named, reusable procedure Claude Code loads on demand instead of carrying around all the time. That last clause is the whole point, and the reason skills beat both fat context files and re-pasted prompts. Here is the anatomy, a working example, and the three mistakes that make a well-written skill never get used.
Why a skill and not another CLAUDE.md paragraph
Everything in CLAUDE.md is loaded into every session and pays context rent every time, needed or not. A skill inverts that: only its name and description ride along by default; the full instructions load when the task calls for them. That is progressive disclosure, and it means a 500-line procedure costs the session two lines until the moment it is actually relevant.
- Rule that applies always (naming conventions, don't touch prod) →
CLAUDE.md. - Procedure for a recurring task (release, review checklist, deploy) → skill.
- One-off instruction → just say it in the prompt.
The full decision matrix — rules vs skills vs one-shot prompts, and why prompted instructions decay while installed ones don't — is in why AI prompts don't stick.
Anatomy of a skill
.claude/skills/deploy-check/ ├── SKILL.md # required — frontmatter + instructions ├── checklist.md # optional supporting files, referenced from SKILL.md └── verify.sh # scripts the skill tells Claude to run
Project skills live in .claude/skills/ (checked in, whole team); personal ones in ~/.claude/skills/ (every project on your machine). The SKILL.md itself is YAML frontmatter plus markdown instructions:
--- name: deploy-check description: Pre-deploy verification for this app. Use before any production deploy, when the user asks to ship, deploy, release, or push to prod. --- # Deploy check Run these gates in order. A failed gate STOPS the deploy. 1. Build must be green: `pnpm build` — no warnings treated as ok. 2. Run ./verify.sh and paste its output into your reply. 3. Check the changelog: every user-facing change since the last tag is listed. If not, write the missing entries first. 4. Deploy is two steps on this project — the deploy AND the alias. Never report "deployed" until the live URL serves the new build: curl the production domain and confirm the new version marker. Full gate list with rationale: read checklist.md.
The description is 90% of whether it ever fires
Claude decides to use a skill by reading its description — that is the entire retrieval mechanism. A description that says what the skill is(“Deployment procedures for this repo”) loses to one that says when to reach for it, in the words a task would actually contain. Write the trigger conditions in: “Use when the user asks to ship, deploy, release, push to prod.” If you find yourself invoking it by name every time because it never auto-fires, the description is the bug.
Name and description are the retrieval index. The body is the payload. Most skills fail at retrieval, not payload.
Writing the body so it survives contact with the model
- Imperative, ordered, checkable.Numbered gates beat prose. “A failed gate stops the deploy” beats “please be careful”.
- Encode the traps.The example above encodes a real one from our own machine — our deploy tool does not move the production domain by itself, so “deployed” without the alias step is a lie that looks green. A skill is exactly the place institutional scar tissue belongs: written once, applied by every future session.
- Demand receipts.“Paste the output” turns each step from a claim into evidence — the cheap version of the verification gates in the “done but isn't” problem.
- Push bulk to supporting files. Keep SKILL.md a lean procedure that points at
checklist.mdor a script for detail — Claude reads those on demand, which keeps the loaded portion small.
Test it like software, because it is
- Direct invocation: type
/deploy-check(skills are invocable by name, like slash commands). Does the procedure execute correctly when explicitly called? - Trigger test:start a fresh session and phrase the task naturally — “let's ship this”. Did the skill fire without being named? If not: rewrite the description, not the body.
- Violation test: set up a state where a gate should fail (broken build, missing changelog) and confirm the skill actually stops there instead of narrating past it. A checklist that cannot fail is decoration.
The whole thing on one screen
location .claude/skills/<name>/SKILL.md (project)
~/.claude/skills/<name>/SKILL.md (personal)
frontmatter name + description — description carries TRIGGER words
body numbered gates · imperative · demand pasted receipts
bulk supporting files next to SKILL.md, referenced by name
test 1) /name direct 2) natural phrasing fires it? 3) can a gate fail?
escalate must-never-skip steps → hooks, not skillsReference: Anthropic's skills documentation. For the standing-context side of the same system — what belongs in the file that loads every session — see how to write a CLAUDE.md file.