# Skills | Claude for Real Work

> Skills in Claude Code: instructions that load only when they are needed, how to write a description that triggers, and how reliably that happens.

Source: https://karakabakov.com/courses/claude-for-real-work/skills/

---

[&larr; Claude for Real Work](https://karakabakov.com/courses/claude-for-real-work/)

# Skills

Lesson 21 of 28 · 14 min · Updated August 21, 2026

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.ts` as 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-report` is 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.md`** if 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: true` and 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-report` appears 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 `/model` and 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.

[&larr; Previous

## Your own slash commands](https://karakabakov.com/courses/claude-for-real-work/slash-commands/) [Next &rarr;

## Subagents](https://karakabakov.com/courses/claude-for-real-work/subagents/)

[&larr; All lessons](https://karakabakov.com/courses/claude-for-real-work/) [Next lesson &rarr;](https://karakabakov.com/courses/claude-for-real-work/subagents/)
