skill-evals
Use when creating, editing, reviewing, or deciding whether to keep a Claude Code skill - builds a small eval harness (happy-path + negative trigger cases, regex/LLM asserts, ablation runs) so skill ch
SKILL.mdskill-evals/SKILL.md
GitHubSkill Evals
Overview
A skill without evals is a vibe check. Agents are nondeterministic, so when a task fails you cannot tell whether the skill is bad or the task was hard. Evals separate those two.
Core principle: don't ship a skill without evals. Ten to twenty prompts and a regex assert file catch most of what goes wrong.
Two things fail, and they fail differently:
- Triggering — the model never loads the skill (or loads it when it shouldn't). ~50% of failures.
- Outcome — the skill loads but the result is still wrong.
Test both.
When to Use
- Writing a new skill, before calling it done
- Editing an existing skill (the eval is the regression gate)
- A skill "sometimes works" and you don't know why
- Deciding whether a skill still earns its tokens (ablation → retire)
- Auditing an AI-generated skill — these frequently degrade performance
Skip only for a throwaway one-off skill you will delete this week.
The Loop
1. Classify the skill capability (temporary) vs preference (durable)
2. Write 10-20 cases ~5 should-trigger, ~5 should-NOT-trigger, rest outcome
3. Baseline (ablation) run WITHOUT the skill -> this is the number to beat
4. Run WITH the skill 3-6 trials per case, isolated workspace each run
5. Read the failures fix the description first, then the body
6. Gate every future edit a change ships only if it improves or holds the score
Classify First
| Kind | Teaches | Lifespan | Eval focus |
|---|---|---|---|
| Capability | Something the model can't do consistently yet (new API, unfamiliar tool) | Temporary — models catch up | Ablation, to detect when to retire |
| Preference | Your workflow, style, conventions, domain rules | Durable — models never learn your team | Regression, to protect against drift |
Capability skills get retired. Preference skills get defended. Keep the eval either way — after retiring a skill, the eval keeps watching for regression, and tells you when to bring the skill back.
Writing Cases
A case is a user prompt plus what must be true afterward. Write prompts the way a user writes them — vague, no skill name, no jargon from your SKILL.md. If every test prompt happens to contain your trigger keywords, you have tested nothing.
Five happy-path. Realistic asks that should invoke the skill. Pull from real transcripts where you can — real data beats synthetic.
Five negative. Adjacent asks that must NOT invoke it. This is the half everyone skips, and it's where over-triggering (wasted tokens, confused agent) shows up. If the skill is for React components, the negatives are Angular, Vue, plain CSS, backend.
Test outcomes, not paths. Assert the task got done. Do not assert the skill was read on turn one — loading on turn five is fine, and not loading at all is fine if the answer is right. The only path assertion that matters is on negative cases, where any load is a failure.
See references/eval-format.md for the case-file schema and assertion types.
Running
scripts/run_evals.py runs a case file against claude -p in an isolated config
directory and workspace, with and without the skill installed.
python scripts/run_evals.py evals.json --skill ~/.claude/skills/my-skill --trials 3
python scripts/run_evals.py evals.json --skill ~/.claude/skills/my-skill --ablation
Rules the runner enforces, and why:
- Isolated runs. Agents cheat. In your real workspace they'll read prior chats, neighboring files, or git history and produce the right answer without the skill. Fresh temp config dir + fresh cwd per run.
- 3-6 trials per case. One green run proves nothing about a nondeterministic system. Report pass rate, not pass/fail.
- Ablation always. With-skill vs without-skill is the only number that says whether the skill does anything.
Asserts: Regex First
Most skill evals are regex. Coding agents write good regex, they cost nothing, and you can rerun them hundreds of times.
"expect": ["from google import genai", "gemini-3"] # must appear
"forbid": ["google\.generativeai", "gemini-1\.5"] # must not appear
Reach for an LLM judge only when correctness isn't lexical — multi-step traces, prose quality, "did it follow the workflow". Give the judge a rubric and a pass/fail verdict, not a 1-10 score.
Reading Failures
| Symptom | Almost always | Fix |
|---|---|---|
| Never triggers | Description too vague or too abstract | Rewrite description: why + how + when, as a directive |
| Triggers on negatives | Description too broad ("web development") | Narrow to the specific artifact ("React components, Tailwind CSS") |
| Triggers, wrong output | Body is passive or over-specified | Directives over essays; goals and constraints over step-by-step |
| Works with skill, works without | Model caught up | Retire the skill, keep the eval |
The description is the highest-leverage text in the whole skill and the cheapest to iterate on. Fix it before touching the body.
Cost Audit
Two costs, two different fixes:
- Description tokens are paid on every model call, whether or not the skill fires. Keep it to a couple of sentences.
- Body tokens are paid whenever the skill loads. Keep SKILL.md under ~500 lines and
push deep material into reference files the model opens only when it needs them
(
references/aws.md,references/gcp.md— not one file with everything).
Strip no-ops. Any instruction that doesn't change agent behavior is money burned: "write clear code", "make it readable", "follow best practices". The model already does that. Delete the line, rerun the evals; if the score holds, it was a no-op. AI-generated skills are full of them.
And if the workflow is genuinely fixed — step 1, step 2, step 3, always identical — it isn't a skill. Write a script and have the skill invoke it.
Common Mistakes
- No negative cases. You'll ship an over-triggering skill and never see it.
- Prompts written from the SKILL.md. They leak trigger words; every case passes; nothing was tested.
- One trial per case. Noise reads as signal.
- No baseline. You can't claim improvement without a number to improve on.
- Running in your real repo. The agent finds the answer without the skill and you score a false pass.
- Asserting the skill loaded on turn one. Tests the path, not the outcome.
- Deleting the eval with the skill. The eval outlives the skill.
- Testing one harness only. Behavior differs across Claude Code / Cursor / Codex and across models. If your team is mixed, test mixed.
Regression Gate
Once evals exist, they are the merge condition: a skill edit ships only if the eval score improves, or holds while adding new cases. That is the whole point — you stop guessing whether last week's tweak helped.
Getting Started
Copy assets/evals.template.json next to the skill you're testing, fill in the ten
cases, then run the ablation. Start there even if the cases feel thin — ten prompts
beats zero, and you will be surprised what the first five surface.
Reference
assets/evals.template.json— starting case file, 5 positive + 5 negativereferences/eval-format.md— case-file schema, assertion types, LLM-judge rubric templatereferences/description-checklist.md— how to write and audit a skill descriptionscripts/run_evals.py— harness (stdlib only, no deps)