Lesson 20 of 32
Skills
Packaging a procedure or a body of knowledge into a file Claude loads only when it is needed, so it costs you nothing the rest of the time.
You now have a file that loads into every session, and you know why keeping it small matters. That constraint creates an obvious problem: what do you do with knowledge that is genuinely useful, but only about one time in twenty?
Putting it in CLAUDE.md means paying for it every session to benefit
occasionally. Leaving it out means re-typing it, or worse, forgetting it exists.
Skills are the answer to that specific problem.
What a skill is
A skill is a directory containing a markdown file called SKILL.md. The file
holds instructions in plain English, and Claude either applies them
automatically when they are relevant, or runs them when you type the skill's
name as a command.
The smallest useful example is a page of conventions:
---
name: api-conventions
description: REST API design conventions for our services
---
- Use kebab-case for URL paths
- Use camelCase for JSON properties
- Always include pagination for list endpoints
- Version APIs in the URL path (/v1/, /v2/)Saved at .claude/skills/api-conventions/SKILL.md, that is a complete,
working skill. There is no build step and no registration.
Skills live in one of two places. .claude/skills/ inside a project is shared
with your team through git. ~/.claude/skills/ in your home directory is
personal and follows you into every project.
That looks like the choice you made for CLAUDE.md, but the resemblance stops
at the paths. CLAUDE.md files accumulate: a personal one and a project one
both load, and you get both. Skills with the same name override instead, and the
direction is worth committing to memory because it is the reverse of what most
configuration does — a personal skill wins over a project one. If a teammate
asks why the shared deploy skill is not behaving as documented, an old
personal skill of the same name is the first thing to look for.
Why the loading behaviour is the whole point
Here is the mechanic that makes skills worth having, and it is easy to miss.
At the start of a session, Claude Code loads a listing of the skills available: their names and their descriptions. That listing is small. The body of each skill — the instructions, the checklist, the twelve paragraphs of domain knowledge — is not loaded. It arrives only when the skill is actually invoked.
- Step 1
Session starts
Claude sees a list of skill names and descriptions. A few dozen words each.
- Step 2
Something matches
You type /skill-name, or your request matches a description closely enough that Claude reaches for it.
- Step 3
The body loads
The full SKILL.md enters the conversation and stays for the rest of the session.
That has a direct consequence for how you write the description. It is not a label — it is the trigger. Claude decides whether to load a skill by matching your request against that sentence, so it needs to contain the words a person would actually use. A description reading "Payment logic" will not fire when you ask about refunds. One reading "How refunds, chargebacks and partial payments work in our billing service" will.
There is one wrinkle worth knowing. Once a skill has been invoked, its content stays in the conversation for the rest of the session. It is pay-on-use, not pay-per-use, which is another reason to keep the body itself lean.
The second kind: a procedure you invoke
The example above is reference material. The other common shape is a workflow — a sequence of steps you run deliberately, which you invoke by typing its name.
The command name comes from the directory. A skill at
.claude/skills/fix-issue/SKILL.md is invoked with /fix-issue, and anything
you type after the name is substituted into the body wherever $ARGUMENTS
appears.
---
name: fix-issue
description: Fix a GitHub issue
disable-model-invocation: true
---
Analyze and fix the GitHub issue: $ARGUMENTS.
1. Use `gh issue view` to get the issue details
2. Understand the problem described in the issue
3. Search the codebase for relevant files
4. Implement the necessary changes to fix the issue
5. Write and run tests to verify the fix
6. Ensure code passes linting and type checking
7. Create a descriptive commit message
8. Push and create a PRRun /fix-issue 1234 and Claude receives that list with the number filled in.
Note the third frontmatter line. disable-model-invocation: true means only you
can trigger this skill — Claude will not decide to run it on its own. That
matters for anything with side effects. You do not want Claude concluding that
the code looks finished and therefore opening a pull request. If it tries anyway,
the call is blocked and it will suggest that you run the command yourself.
The mirror image exists too. user-invocable: false hides a skill from the /
menu while leaving Claude free to use it, which suits background knowledge that
is not a sensible thing for a person to "run" — an explanation of a legacy
system, say.
| Frontmatter | You can invoke | Claude can invoke | What sits in context |
|---|---|---|---|
| default | Yes | Yes | The description |
disable-model-invocation: true | Yes | No | Nothing |
user-invocable: false | No | Yes | The description |
That last column is a small bonus worth knowing. Claude only needs a description in order to decide whether to load something, so a skill it is not allowed to load does not need one in context at all. A manual-only skill is therefore free until the moment you type its name — not one sentence's worth of context, but nothing.
Of the frontmatter fields, only description really earns your attention.
name is a display label shown in listings — for a project or personal skill it
does not change the command you type, which still comes from the directory.
Everything else is optional.
Writing one
You do not have to author these by hand, and mostly you should not. Describing the workflow to Claude and letting it write the file is faster and produces better frontmatter than guessing.
You typed: Every time I add a database migration I have to remember four things: name the file with a timestamp prefix, write a down migration, run it against the local database, and check it into the same commit as the model change. Make that a skill.
Claude replied: That's a procedure with side effects, so I'll make it manual-only rather than something I run when I think it applies. Writing it to .claude/skills/add-migration/SKILL.md.
Claude ran: Write .claude/skills/add-migration/SKILL.md
Output: --- name: add-migration description: Create a database migration following this project's conventions disable-model-invocation: true argument-hint: [description of the change] --- Create a migration for: $ARGUMENTS 1. Name the file `<unix-timestamp>_<snake_case_description>.sql` 2. Write both the up and the down migration 3. Run `npm run db:migrate` against the local database 4. Confirm it applied, then run `npm run db:rollback` and re-apply to prove the down migration works 5. Stage the migration alongside the model change in one commitWhyStep four is not something you asked for. Claude added a verification step — the down migration is now tested rather than merely written. Worth reading a generated skill before trusting it, in both directions.
You typed: /add-migration add a nullable last_seen_at column to users
Claude ran: Write migrations/1786239140_add_last_seen_at_to_users.sql
Claude ran: Run: npm run db:migrate
Output: Applied 1 migration in 84msClaude ran: Run: npm run db:rollback
Output: ERROR column "last_seen_at" does not existWhyThe down migration was wrong. This is the skill earning its place — the check you always meant to do, and usually skipped, ran automatically because it was written down once.
Claude ran: Edit migrations/1786239140_add_last_seen_at_to_users.sql
Claude ran: Run: npm run db:migrate
Output: Applied 1 migration in 79msClaude replied: Fixed — the down migration was dropping the wrong table name. Rollback and re-apply both clean now. Ready to stage with the model change.
Claude Code watches the skill directories, so a file you add or edit is picked up within the session without a restart. The exception is the one that catches people writing their first skill: if the skills directory itself did not exist when the session started, there is nothing being watched yet, and you do need to restart before the skill appears.
Supporting files, and knowing when to stop
A skill is a directory rather than a single file for a reason. You can put other
things beside SKILL.md — a template to fill in, an example of the expected
output, a longer reference document, a script Claude can execute. Mention them
from SKILL.md so Claude knows what each one holds, and it will read them when
it needs them rather than up front.
The guidance is to keep SKILL.md itself under 500 lines and move detail into
those separate files. The reasoning should be familiar by now: the entry point is
what gets loaded, so it should be navigation rather than content.
Two honest limitations. First, automatic invocation is a judgement, not a rule.
Claude decides whether your request matches the description, and it will
sometimes decide wrong in both directions — a skill that fires when you did not
want it, or sits unused while you wonder why. When a skill must run, invoke it by
name rather than hoping. Second, if you accumulate a large number of skills, the
listing itself has a budget, and descriptions can be shortened to fit. /doctor
will give you an estimate of what your skills are costing.
If a skill is not triggering, the first thing to check is the description. Try saying your request out loud, then look at whether those words appear in it. You can also just ask, in a session: "what skills are available?"
What to take away
A skill is a directory with a SKILL.md inside it, holding either reference
knowledge Claude applies when it fits, or a procedure you invoke by typing the
directory name as a command. Its value comes from the loading behaviour: only
the description sits in context at session start, and the body arrives when the
skill is used, which makes it the right home for anything that is important but
only sometimes relevant. Write the description in the words you would actually
say, since that is what triggers it. Use $ARGUMENTS to pass in a value, and
disable-model-invocation: true for anything with side effects you want to
control the timing of yourself. The fastest way to make one is to describe the
procedure to Claude and let it write the file.
Next: hooks, which cover the case a skill cannot — something that must happen every single time, whether or not Claude thinks it should.
Check yourself
5 questions · pass 4/5 to unlock Hooks
1.Why is a skill a better home than CLAUDE.md for knowledge that only matters occasionally?
2.What does
disable-model-invocation: truedo to a skill?3.A skill lives at
.claude/skills/deploy-staging/SKILL.md. What do you type to run it?4.What is
$ARGUMENTSfor in a SKILL.md?5.Claude is not picking up a skill you expected it to use. What should you check first?
5 left to answer