Skills
A skill is the same markdown file as lesson 20’s command, in a directory instead of a file,
with one addition: a description that tells Claude when the skill is relevant, so it can
load the skill without you typing anything.
That one addition is the whole subject. It is also the part that does not always work, and this lesson is going to be specific about when.
The gap it fills
Lesson 15 said procedures do not belong in CLAUDE.md. Here is why, concretely.
Adding a report to stockroom has five steps and about twenty lines of instruction. Put it
in CLAUDE.md and every session pays for those twenty lines, including the four hundred
sessions that are not adding a report. Put it in a skill and it costs a one-line
description until the day somebody adds a report.
The skill below describes
src/reports/index.tsas one file with three error conventions in it, which is how it arrives from a fresh clone. If you did lesson 14’s exercise you split it, so adjust the wording to your repo as you write it. That adjusting is not busy work: a skill that describes a layout the codebase no longer has is the most common way a skill goes quietly wrong, and you are about to feel why.
That is the trade in one sentence: CLAUDE.md is what is true always, a skill is what is
needed sometimes.
Write one
mkdir -p .claude/skills/add-report
.claude/skills/add-report/SKILL.md:
---
name: add-report
description: How to add a new report to stockroom. Use whenever someone wants to add,
create or write a new report, a new metric, or a new figure for the office, or asks
where report code belongs.
allowed-tools: Read Grep Glob
---
Adding a report to stockroom is five steps, in this order.
1. Write the function in `src/reports/index.ts`, under its own banner comment. It takes
`store: JsonStore` first and returns plain data, never formatted text.
2. Pick an error convention and say why in a comment. The file already has three:
`history` throws, `valuation` returns `null`, `variance` returns `{ ok: false }`.
Match the closest neighbour rather than inventing a fourth.
3. If it is a list, page it with `paginate()` and `loadConfig().pageSize`. Do not slice
by hand.
4. Add a `render*` function in the same file for the CLI, and a `case` in the switch in
`src/cli/main.ts`. Update the `USAGE` string in the same commit.
5. Add tests in `test/reports.test.ts` using `tempStore` from `test/helpers/store.ts`.
Money is whole cents. Timestamps carry an offset.
Do not add a route to `src/server/main.ts` unless asked. The HTTP view is read-only and
deliberately small.
Note what is in there: the conventions somebody would get wrong. Not “write a function and test it”, which it would do anyway, but “there are already three error conventions in that file and you should match the nearest one rather than adding a fourth”. A skill that only contains what a competent person would do unprompted is a skill that costs context and changes nothing.
Now check it exists
Before testing whether it triggers, check it is registered at all. This part is deterministic:
claude -p "/context"
### Skills
| Skill | Source | Tokens |
|------------|---------|--------|
| add-report | Project | ~70 |
| failing | Project | ~20 |
If it is not in that table, nothing else in this lesson applies. Malformed YAML is the usual
cause, and it fails quietly: Claude Code loads the body with empty metadata, so /add-report
still works while automatic loading never happens. Run with --debug to see the parse
error.
Then check it works when told:
/add-report inventory turnover
Direct invocation always works. That is your baseline.
And now the honest part
Automatic invocation is a judgement the model makes from your description. It is not a lookup, and it is not guaranteed.
The same skill, the same description, the same question, on two models:
I want to add a report that shows inventory turnover. Where does the code go?
On Sonnet the skill loaded and the answer came back in the five steps above, in order,
including the warning about src/server/main.ts. On Haiku it did not load, and the answer
was a reasonable-sounding two sentences that missed four of the five steps.
Neither model was wrong to do what it did. The description is a hint, and acting on hints is one of the things a bigger model is better at. Which means:
- If it matters that the instructions are followed, type the name.
/add-reportis one word and it removes the question. - Test triggering on the model you use, with the phrasing you use.
- Do not debug a skill’s content when the problem is that it never loaded. Ask
“what skills are available?” or add a marker line to the skill body, like
Begin your answer with [add-report], so you can see it fire.
That marker trick is worth keeping permanently in skills you rely on. A skill that silently does not load looks exactly like a skill whose instructions were ignored, and they need different fixes.
How do you make a skill trigger more often?
The description is the only thing that decides. Two rules.
Use the words you would say. The description that works contains “add a report”, “new metric”, “where does report code go”, because those are things a person types. A description that says “provides guidance on the report subsystem architecture” contains nothing anybody says out loud.
Put the main case first. Descriptions are truncated when you have a lot of skills, and they are truncated from the end. The first clause is the one that survives.
If a skill fires when you do not want it, make the description narrower, or set
disable-model-invocation: true and type it yourself. If it never fires, widen it and
check with the marker.
Bundled files
A skill is a directory, so it can hold more than SKILL.md:
.claude/skills/add-report/
├── SKILL.md
└── example-report.ts
SKILL.md can point at the sibling file: “Follow the shape of example-report.ts in this
directory.” Claude reads it when it needs it, which means a 300-line reference example
costs nothing until the day it is relevant. This is the mechanism that makes skills
worthwhile for anything long.
Which of the four
You now have four places to put instructions and they overlap. The test that separates them:
CLAUDE.mdif it is needed in every session. Build commands, conventions, traps. Costs context always.- A skill if it is needed occasionally and is longer than a line. Procedures, checklists, reference material. Costs a description until used.
- A command if you always know when you want it. Same file format; you just type the
name. Add
disable-model-invocation: trueand a skill becomes exactly this. - A hook if it has to happen whether or not Claude cooperates. That is lesson 23, and it is the only one of the four that is enforcement rather than persuasion.
The mistake worth avoiding: a CLAUDE.md that has grown a “How to add a new endpoint”
section. That is a skill that has been left in the wrong file, and it is being paid for on
every session.
Your turn
Write the add-report skill above into your stockroom clone. Then run the three checks in
order.
claude -p "/context" | grep -A5 "### Skills"
Check 1, registration:
add-reportappears in the Skills table with a token count. If it does not, your frontmatter is malformed.
Then, in a session, type /add-report inventory turnover.
Check 2, invocation: the answer contains all five steps in order, including the instruction not to touch
src/server/main.ts.
Now add Begin your answer with the line [add-report] to the bottom of the skill body, and
in a fresh session ask without naming it:
I want to add a report that shows inventory turnover. Where does the code go?
Check 3, triggering: either
[add-report]appears and the skill loaded, or it does not. Both are real answers. Run it again on a different model with/modeland see whether the result changes. Whatever you find, you now know something about your own setup that you cannot get from documentation.
Recap
A skill is a command with a description, in a directory that can hold supporting files. It costs a one-line description until it is used, which is what makes it the right home for anything long.
Put the conventions somebody would get wrong in it, not the things anyone would do anyway.
Verify in three steps and keep them separate: registered (check /context), works when
named (type /name), loads on its own (a marker line in the body). Only the third one is a
judgement, and it varies by model and by phrasing.
CLAUDE.md for always, a skill for sometimes, a command for when you know, a hook for when
it has to happen.
Next: handing work to a second context, what it saves, and the thing it does not save.