p4ni.

Research

Claude Code Skill Frontmatter: 27 Runs on What Actually Loads a Skill

· 6 min read

On this page

Search for claude code skills yaml frontmatter and the first page is mostly official documentation, plus one Reddit thread that outranks half of it: “TIL: Skill descriptions must be single-line in YAML frontmatter.” The author had spent hours debugging skills that were never picked up, and traced it to a multi-line description.

That claim is now repeated in Substack posts, LinkedIn carousels, and at least one YouTube tutorial. It is the kind of folklore that spreads because it is actionable and because nobody wants to spend an afternoon testing it.

I spent the afternoon. The format is irrelevant — a multi-line description loads fine. What actually decided every single tie in my runs was the skill’s directory name, which I have not seen mentioned anywhere.

The setup

Seven skills, identical in every respect except the description line. Same body, same task (convert CSV to a Markdown table), same everything else.

The naming matters more than it looks. Call a skill csv-to-markdown and it will fire on the name alone, drowning out whatever the description says. So every skill got a deliberately meaningless name — probe-alpha through probe-golf — leaving the description as the only signal that carries information.

Each body contains one instruction:

When this skill runs, output exactly this as the first line:

CANARY-ALPHA

A canary string is the only honest way to measure this. Asking the model “did you use the skill?” gets you a confident guess. Worse, Claude will sometimes announce that it is using probe-alpha without the canary ever appearing — it did this to me twice. The announcement is not evidence. Only the string is.

The seven descriptions:

IDDescription style
ALPHAEnglish, one line, capability only — no trigger phrase
BRAVOALPHA plus Use when the user asks to convert a CSV into a Markdown table.
CHARLIEJapanese, with explicit trigger phrases in quotes
DELTAByte-identical content to BRAVO, formatted as a YAML block scalar
ECHOEnglish, plus seven synonyms and adjacent phrasings
FOXTROTFour words: CSV to Markdown table.
GOLFEighty-plus words of verbose prose

DELTA is the whole point. It says exactly what BRAVO says, differing only in whether the YAML is one line or folded. If the Reddit claim holds, BRAVO fires and DELTA never does.

Every run is a fresh session via claude -p, so nothing leaks between trials:

cd skill-probe
claude -p "convert this CSV into a markdown table"

Twenty-seven runs against Claude Code 2.1.241 on macOS.

Round one: which description wins

Four phrasings of the same request, each run four or five times.

RequestFiredWinner
CSV を Markdown の表に変換して (exact match, Japanese)5/5CHARLIE
このカンマ区切りのデータ、表の形にしたい (paraphrase, Japanese)3/5CHARLIE
data.csv、README に貼れる形にしたい (oblique, Japanese)3/4ECHO
convert this CSV into a markdown table (exact match, English)4/4BRAVO

Three of the seven skills were never called once: ALPHA, FOXTROT, and GOLF. A description with no trigger phrase, a four-word description, and an eighty-word description all lost every trial to descriptions that named the situation explicitly.

Two things are worth pulling out of that table.

Firing is not deterministic. The same request, sent to identical setups in separate sessions, sometimes loaded a skill and sometimes did not. My sample is four or five runs per phrasing, which is enough to say the behaviour wobbles and not enough to put a percentage on it. If your skill fires when you test it, that is not proof it will fire tomorrow. This is why my own project instructions say a certain skill must always be used — I had been compensating for this without knowing why.

Vocabulary is matched, not meaning. The paraphrase that failed twice — “this comma-separated data, I want it as a table” — is unambiguous to any human reader. ECHO’s description contains comma-separated values, which looks like it should catch exactly that. It did not, because ECHO’s synonyms are in English and the request was in Japanese. Meanwhile ECHO won the oblique request, “make data.csv something I can paste into a README,” because its description happens to contain pasting data into a README.

The synonym list works. It just works literally, in the language you wrote it in.

On one English run Claude volunteered its own reasoning: it noted that seven overlapping CSV skills were installed and said it picked probe-bravo because that one matched the request wording exactly. Surface-level string agreement, by its own account.

Round two: is the Reddit claim true

In the full seven-skill setup, the scoreboard looked like a clean confirmation:

  • BRAVO (single line): 4/4
  • DELTA (block scalar): 0/4

Which is exactly what you would blog about if you stopped there. So I kept going.

Remove BRAVO from the directory. DELTA fires 3/3. A multi-line description loads perfectly well; it had simply lost every head-to-head against an identically-worded competitor.

That leaves the real question: why does BRAVO beat DELTA when they say the same thing? One obvious confound is the directory name. probe-bravo sorts before probe-delta.

Swap the two descriptions, so probe-bravo now carries the block scalar and probe-delta carries the single line. If format decides, the winner should flip to probe-delta.

It did not. probe-bravo won 3/3 — now with the multi-line description.

Rename probe-bravo to probe-xray, leaving its multi-line description untouched. Now probe-delta sorts first.

probe-delta won 3/3.

ConditionResult
All seven installedBRAVO 4/4, DELTA 0/4
BRAVO removedDELTA 3/3
Descriptions swappedprobe-bravo 3/3 (now multi-line)
probe-bravo renamed to probe-xrayprobe-delta 3/3

The winner tracked the name every time and the description format never once. When two skills match a request about equally well, the one whose directory name sorts earlier wins. The single-line folklore is an artifact of exactly this: people compare a working skill against a broken one, the names happen to fall a certain way, and the difference gets attributed to the visible thing.

I would not build on the alphabetical ordering as a documented guarantee — it is an observed behaviour in 2.1.241, not a promise, and it is the sort of thing that changes without a release note. What it does tell you is that ties are broken by something arbitrary, so the fix is to stop producing ties.

What this means for writing descriptions

Nothing here contradicts the official documentation, which is why the documentation does not help with this problem — it tells you the schema, not what wins.

  • Name the situation, not the capability. Converts CSV data into a Markdown table never fired. Adding Use when the user asks to... to the same sentence made it a consistent winner.
  • List the phrasings people actually use, in the language they will use them in. The synonym-stuffed description was the only one that caught an oblique request. It was also useless against a paraphrase in a language its synonyms did not cover.
  • Do not write four words, and do not write eighty. Both extremes lost every trial to a description that was one clear sentence plus a trigger clause.
  • Line breaks are fine. Write the description however it reads best.
  • Overlapping skills are the actual failure mode. If two of your skills could plausibly answer the same request, you have handed the decision to name ordering. Narrow one of the descriptions until they stop competing.
  • A skill that fires in testing may not fire in production. If a step genuinely must happen every time, say so in your project instructions rather than trusting the description to win on its own.

The method here is the same canary approach I used to check whether Claude Code reads AGENTS.md, and it reached the same shape of answer: the widely-repeated claim was wrong, and the real mechanism was one nobody had looked for. Plant a fact that exists nowhere else, disable every way for the model to cheat, and run it enough times to see whether the result holds still.