The Skill That Never Fired A skill can fail in two ways.
Its instructions can be wrong, so it does the job badly.
Or Claude can decide never to load it, so the instructions never run at all.
The first failure is obvious when you test the skill by name.
The second only shows up when you test whether Claude chooses it on its own.
The second failure is the quiet one.
You write a skill, you invoke it by name to check it, and it works.
Then in normal use it just sits there.
Claude answers without it.
Nothing errors, nothing warns you, and the skill still shows as installed.
It was never wrong.
It was never chosen.
That choice is a routing decision, and Claude makes it by matching the request against your skill's name and description, before it reads a word of the body.
The Claude Code docs say it directly: the description is what helps Claude decide when to load a skill.
Anthropic tells you to test that decision, separate from the skill's output, and ships a tool that does it.
Its scores one target skill over repeated runs: does this skill fire on the prompts it should, and stay quiet on the ones it should not?
What that score does not tell you is what happened when another plausible skill was there too: whether the neighbour took the request, both fired, or neither did.
That is the failure this piece is interested in, where your skill sits beside one that could answer it and the winner is not guaranteed to be yours.
This walks through building a skill, watching that decision for yourself, and grading it against the neighbour it can lose to.
You can run a first pass in about 15 minutes at a terminal.
What a skill is At its simplest, a skill is a folder with one required file, .
It can also hold scripts and reference files that load only when needed, but the minimum is the one file: For a normal auto-invocable skill, the name and description sit in Claude's discovery context so it can decide whether the skill is relevant.
The body below the frontmatter loads only when the skill is invoked, whether Claude chooses it or you type its name.
Claude sees both the name and the description, and the description is the main field Anthropic gives you for saying when the skill should run.
Write it for the router, not as a note to yourself.
Claude Code also accepts a field, appended to the description; these skills use only a name and a description.
Three common ways to use a skill.
In claude.ai, turn on code execution, open Customize then Skills, and upload the folder as a zip.
In Claude Code, put the folder in for one project or for all of them.
Through the Claude API, you upload it and reference its .
The core format travels across all three, though installation differs and some frontmatter, including the used later, is specific to Claude Code.
Watching the routing decision The mistake to avoid is judging a skill by its output.
Ask Claude to format a date and you might get whether your skill ran or not, because the model can format a date on its own.
The output tells you nothing about routing.
You want the decision itself.
In Claude Code, a skill runs through a tool that appears in the event stream.
Run a prompt non-interactively and filter the stream down to the skill call: That last line is the filtered result, not the raw stream, which wraps each event in more metadata.
If the reader prints nothing, dump a raw event and look for a call by hand, because the stream's shape shifts between versions.
It is the routing decision read from the tool call, not guessed from the output. shows which skills are available to Claude and shows the discovery listing's context cost, but neither proves this prompt invoked one.
In claude.ai there is no equivalent machine-readable event.
Anthropic's guidance is to review Claude's thinking to confirm a skill loaded, which works for checking by eye but not for building the kind of record above.
And the event stream shows the skills Claude actually invoked, both of them when it invokes two, which is how a shows up at all.
What it does not expose is the candidate set: the other installed skills that were plausible but never invoked.
You see what fired, not what it beat.
Routing is a decision you can grade Whether a skill fires is a choice among whatever skills could plausibly answer the request.
You can only grade that choice if you know what the right answer was before you run it.
So I built two skills with different jobs. , above, formats dates for customer emails in long form. formats them for CSV exports as .
Then I wrote a labelled prompt set: 4 requests that clearly want the customer skill, 4 that clearly want the export skill, and 4 date-adjacent requests that should fire neither.
Every result gets one of four labels: right, wrong, none, or both.
Start with the failures, because they are where the method earns its keep.
Ambiguous request.
Ask "Format this date: 2026-08-30" with both skills installed, and the results scatter: sometimes one fires, sometimes both, sometimes neither.
That scatter is the expected result of an ambiguous request.
The request never said whether it wanted the customer or the export format, so there is no correct answer to grade against.
An ambiguous prompt is not a failed test, it is an ungradable one.
If you cannot label the right skill before running it, the result cannot tell you whether Claude chose well.
Names and descriptions that draw no line.
I named two skills for their output format, and , and gave them the same vague description, "Format a date." Their bodies did different things, but their discovery metadata claimed the same job, so there was no boundary for the router to use and nothing told Claude which one fits a customer request.
The grades went bad in the way that matters: one customer prompt fired nothing at all, and 2 export prompts fired both skills at once.
Misses and double-fires, which is why "both" has to be one of your outcome labels.
Then the control, so you can see what clean looks like.
Give the two skills distinct, use-case descriptions, and ask prompts whose wording matches those use cases, and routing is clean: 8 out of 8 to the right skill, and the neither-prompts correctly firing nothing.
That is the model doing the keyword and intent matching you made easy for it.
It is the case that should work, and it does.
Note that the customer prompts contain words like "customer email" that are already in the customer skill's description.
Clean routing here is a control condition, not proof that routing is robust.
The evidence is in the failures above.
The fix Routing runs on the discovery metadata Claude can see, which is the name and the description together, and my runs show either one can carry it.
When the names were the vague part but the descriptions were sharp, routing was clean.
When the descriptions were the vague part but the names said the use case, and , routing was also clean, 8 out of 8 on the same prompts.
That name-only run is an easy case, mind: the prompts carry the same words as the names, customer and export, so it shows a name helps when the request echoes it, not that a bare name is a strong signal on its own.
It broke in one condition only: format-only names, and , plus a shared vague description, where neither field told Claude what set the two skills apart.
That broken condition is the useful one.
I left the weak names alone and rewrote only the descriptions around use cases, and the mess went to 8 out of
8.
A good description rescued names that carried no signal.
A good name had already done the same for descriptions that carried none.
What you cannot do is leave both vague and expect Claude to find the line.
So write the description as a routing rule, not a summary.
Put the use first.
Include the words people actually type when they want this skill.
Draw the boundary against the neighbour it might be confused with.
Some skills should not be auto-routed at all.
Anything with a side effect or a real cost is safer as a skill you invoke by name, , or one you
