SKILL 07 / META · DEEP DIVE

Writing Evals for a Skill

The official "build evaluations first" finally has a built-in runner

2026-09-08 · BigCat

You revised your SKILL.md, ran it once, and it felt better. What is that feeling worth? Zero — unless you have a baseline, machine-checkable assertions, and a run count greater than one.

Prerequisite → Skill 06 Reading the Official Authoring Guide (this issue opens up the evaluation section everybody skips)

// WHY THIS MATTERS

Anthropic puts "Create evaluations BEFORE writing extensive documentation" in bold at the top of the evaluation section of Skill authoring best practices. Five steps follow, plus a JSON structure — and then a Note: there is not currently a built-in way to run these evaluations; users can create their own evaluation system. That is why the entire section gets skipped.

The Note has fallen behind the tooling. Claude Code's claude plugin eval is now a complete runner with a baseline arm, several grader types, and an HTML report (in early access as of this writing). This issue names all three viable paths, gives the commands and the file layout I actually scaffolded, and then answers the real question: how do you know a change is a genuine improvement rather than the model having a good day?

// 01

What it takes off your plate: killing "I think this version is better"

Claim: skill output is non-deterministic, so a single side-by-side comparison carries no information. You need exactly three things — a baseline, a set of machine-checkable assertions, and a repeat count above one. All three paths below give you those. They differ in cost by an order of magnitude.
THREE PATHS (ordered by cost to get started) ──────────────────────────────────────────────────────────────── A claude plugin eval built-in CLI · a case is a directory ships a baseline arm · status: early access B skill-creator official plugin · subagent per case mean +/- stddev + HTML viewer · generally available C skillOverrides, by hand zero dependencies · the fallback the official docs themselves recommend · always available ──────────────────────────────────────────────────────────────── All three measure the same two things (source: code.claude.com · Skills) 1. does the skill fire on the prompts it should? 2. once it fires, does the output actually get better? "Seeing a skill trigger tells you Claude found it, not that it did what you intended."

The first thing you can actually run

# Path A: the built-in runner. Scaffold a case skeleton from a plugin/skills root
claude plugin eval init --bare my-first-case
# -> writes evals/my-first-case/prompt.md and evals/my-first-case/graders/criteria.md

# Fill them in, then run the current directory three times with a self-contained report
claude plugin eval . --runs 3 --report ./eval-report.html

# Path B: install the official skill-creator and let it drive the whole loop
/plugin marketplace add anthropics/claude-plugins-official
/plugin install skill-creator@claude-plugins-official
/reload-plugins
# then just say: evaluate my summarize-changes skill with skill-creator

# Path C: the fallback. Turn the skill off in .claude/settings.local.json, rerun in a fresh session
{ "skillOverrides": { "summarize-changes": "off" } }

# Five-second checkup before any of this: did the frontmatter parse at all?
claude plugin validate .claude/skills

What init --bare emits is smaller than you would guess: prompt.md carries only max_turns and allowed_tools in its frontmatter, graders/criteria.md only type and weight, with a one-line TODO under each. A case is a directory, not a config file: one folder per case, holding a prompt and a graders/ directory.

Failure mode: as of this writing (Claude Code v2.1.263) plugin eval is still in early access. On an account without it, the command parses your flags and then exits, printing a single line — "plugin eval is currently in early access". It runs nothing, and it fails quietly enough that you may not notice. Do not depend on it silently in CI; confirm by hand that it really runs first.
// 02

The mechanism: how a feeling becomes a number

Claim: everything that makes this work comes from three design decisions — always run an arm without the skill, keep "it fired" out of the score, and split graders into free ones and ones that cost money. None is decoration; each blocks a specific way of fooling yourself.

1. The baseline arm is on by default, not opt-in

--ablation defaults to with-without: whenever a plugin resolves, the runner adds an arm with no plugin at all and reports the score delta. Why must this be the default? Because the model is already strong. Your skill says "write commit messages in conventional-commits form" — the model may well do that with no skill loaded. Without the second arm you would book that point to yourself. skill-creator states the same rule even more bluntly: the with-skill and baseline runs must be spawned "in the same turn". No running the with-skill batch today and backfilling baselines next week.

2. Firing is not quality: with-only graders stay out of the score

This is the sharpest piece of the design. Under with-without, graders marked with-only — including tool_used: Skill — count as an indicator that the plugin fired, not as part of the score. The reasoning is plain: the arm without the skill can never fire the skill, so folding that into the delta is a free point you award yourself. Trigger rate and output quality must be measured separately, which is exactly why skill-creator has a distinct description-tuning step: it generates should-trigger and should-not-trigger prompts, measures the hit rate, and proposes description edits.

3. Two classes of grader: exhaust the free ones first

Put an invalid type in a grader and the CLI lists every legal value: regex | tool_order | tool_used | file_exists | llm | baseline. The first four are pure program checks — free, and they add no variance. The last two are model calls (--judge-model defaults to haiku). That line decides how you design cases: if success can be stated as "produced output.txt", "read before it edited", or "the output contains feat(", do not restate it as a vague "the response should be professional and clear" for an LLM to interpret. It is also precisely the official evaluation principle — more questions with slightly weaker automated grading beats fewer questions graded by hand.

4. The repeat count is 3 by default

--runs defaults to case.runs ?? 3. A single winning run tells you almost nothing in agent evaluation, because the same prompt can produce wholly different trajectories twice in a row. skill-creator carries this into the report: python -m scripts.aggregate_benchmark produces benchmark.json and benchmark.md giving pass_rate, time and tokens per configuration as mean +/- stddev, plus the delta. Read the stddev before the delta — a difference drowned in the standard deviation is not an improvement.

Failure mode: assertions so loose that both arms pass 100% of the time, the delta sits at zero, and you conclude the skill is useless — when in fact the questions have no discriminating power. skill-creator's agents/analyzer.md exists to force an analyst pass over exactly these two diseases: non-discriminating assertions (pass with or without the skill) and high-variance evals (three runs of one configuration scatter wildly, usually a flaky case rather than a flaky skill).
// 03

Real usage and boundaries: five steps, three cases, two Claudes

Claim: the official ordering is counterintuitive — fail first, write cases second, measure the baseline third, and write SKILL.md last. Reverse it and you are documenting requirements you imagined.

The five steps (source: Skill authoring best practices · Evaluation and iteration)

  1. Identify gaps: run real tasks with no skill and write down exactly where it failed and what context was missing.
  2. Create evaluations: build three scenarios around those gaps. skill-creator words it as "2-3 realistic test prompts — the kind of thing a real user would actually say", and explicitly says to write only the prompts first and draft the assertions later, while the runs are in flight — because you do not yet know how it will go wrong.
  3. Establish baseline: measure performance without the skill.
  4. Write minimal instructions: just enough to close the gaps and pass the evaluations, then stop.
  5. Iterate: run the evaluations, compare against baseline, refine.

Claude A / Claude B, and why the session must be fresh

The official method is two instances: Claude A helps you write and revise the skill, Claude B has it loaded and does real work, and you carry B's failures back to A. The docs give a concrete case — "I asked B for a regional sales report; it wrote the query but forgot to filter out test accounts, even though the skill mentions that rule" — and A might respond by changing "always" to "MUST", or by reordering sections.

"Fresh session" is a hard requirement, not a flourish: the docs say plainly that context left over from authoring the skill will mask gaps in the written instructions. You think it understood the file; it actually remembers what you said out loud. Both claude plugin eval and skill-creator run cases in subagents for this reason — every case gets a clean context.

Three tools, three different measurements — do not confuse them

Failure mode: treating /skill-doctor as a quality evaluation. A skill that was "never invoked" is usually not useless — its description never survived selection. That is a triggering problem: fix the description and run the should-trigger set, do not rewrite the body. Conversely, a skill that fires every time and makes things worse every time looks perfectly healthy in /skill-doctor.
// 04

The cost, and the alternatives: evals cost more than skills, and they rot

Claim: a serious skill eval usually costs more time and money than writing the skill did. So the real question is not "should I write evals" but "does this skill deserve a suite" — and most do not.

The honest costs

What to use instead

Failure mode: building an eval suite for a one-off skill. The value of evaluation comes from reuse — it is there to catch regressions on every future edit. For a skill you will never touch again, the suite is pure loss.

// THE ONE-PAGE BUYING CARD

Do these three first: (1) claude plugin validate .claude/skills — five seconds to confirm the frontmatter did not silently break; (2) claude plugin eval init --bare <name> for the skeleton, push as much of your success criteria as possible into the free graders regex / file_exists / tool_used, then claude plugin eval . --runs 3 --max-cost-usd <your ceiling>; (3) two weeks later run /skill-doctor and treat the never-invoked ones as description problems.

Pick a path by situation: want a report a human can read, with mean +/- stddev → install skill-creator@claude-plugins-official; want CI and an exit code → claude plugin eval --threshold (confirm early access first); evaluating raw prompt quality rather than skill triggering → promptfoo; just want to know whether it got better → skillOverrides: off and two fresh sessions by hand.

Numbers to keep: --runs defaults to 3 · --ablation defaults to with-without · --threshold defaults to 1.0 · --judge-model defaults to haiku · free graders are regex / tool_order / tool_used / file_exists, paid ones are llm / baseline · cases live in evals/ by default · results land in evals/results/<timestamp>/.

How to read the report: stddev before delta; a delta drowned in the stddev is not an improvement. Any case where both arms score full marks should be deleted and rewritten — it discriminates nothing.

// GLOSSARY

baseline arm / ablation
The control run: the same case again, with no skill. Without it you are measuring what the model could already do.
grader
The rule that decides whether one run passed. Program-checked graders are free and add no variance; LLM graders cost money and bring their own noise.
LLM-as-judge
Having a second model score the output. For criteria like "professional" or "clear" that no regex can express.
non-discriminating assertion
An assertion that passes with or without the skill. It pins the delta at zero and makes a working skill look useless.
variance
How much repeated runs of one configuration scatter. It is usually large in agent evaluation, which is why the default is three runs and why you read the stddev.
with-only grader
A grader that only makes sense on the arm that has the skill (such as tool_used: Skill). It indicates firing and is excluded from the score delta.

// FURTHER READING