Testing a Claude Code plugin has meant one thing so far: load it with
--plugin-dir and try it. That tells you the plugin can work. It says
nothing about how often it works, whether a rubric change or a new model
quietly broke it, or whether the plugin is doing anything at all versus
Claude just being capable enough to get there on its own.
Claude Code 2.1.269, shipped September 11, 2026 per the official changelog, closes that gap:
Added
claude plugin eval: run a plugin's eval suite against Claude Code and get scored, reproducible results (JSON + HTML report); seeclaude plugin eval --help
What it actually measures
An eval suite lives in an evals/ directory inside the plugin. Each case is
a subdirectory holding a prompt.md - a realistic request a user might type
- and one or more graders. The
plugin evals documentation
lists six grader types:
regex,tool_used,tool_order, andfile_existsare computed from the transcript and cost nothing;llmandbaselinecall a judge model and add to the run's cost. A grader can check that a specific skill fired, that a file came out matching a pattern, or that a second model's rubric verdict was PASS.
The part that makes the score mean something is the baseline. By default
every case runs twice: once with the plugin loaded, once without. The
difference between the two scores, written Δ, is what the plugin actually
contributed. The docs are explicit about what a tie means: "If a case
scores 1.0 both with and without the plugin, the plugin isn't what made it
pass." Without that control, a high score could just mean the prompt was
easy.
That doubles the cost of a run, so claude plugin eval init exists to
generate a first suite for you: it reads the plugin, proposes prompts that
should and shouldn't trigger it, drafts graders, and pilots each one before
writing the case files.
Built to sit in CI, not just a terminal
The design clearly targets automation, not one-off checks. claude plugin eval writes a versioned aggregate-result.json with a schemaVersion field
so a gating script doesn't break when new fields get added. A --threshold
flag (default 1.0) fails the run - and the process exit code - when any
case scores below it. A --trust-plugin flag skips the interactive trust
prompt that would otherwise stall a non-interactive job. And the docs
specifically warn to pin --model in CI, "so a model rollout isn't mistaken
for a plugin regression" - a sign the team building this expects it to run
on every PR, repeatedly, against a moving model target.
That's a real and useful thing to have. A plugin's skill descriptions can degrade in ways nothing else catches - a rubric that used to trigger cleanly stops firing after a wording change, or a new model interprets a prompt differently than the one the suite was written against. Eval turns that from a vague feeling into a number with a sign.
What the score doesn't tell you
Read the security section of the same doc closely and the boundary is
explicit: claude plugin eval "loads the target plugin's skills and hooks
and runs its eval suite on your machine, as you." It evaluates whatever is
on disk at the plugin's path right now - a checkout, an installed copy, a
--plugin-dir folder - not a specific pinned revision of it. The JSON
result records costUsd, durationSeconds, and claudeVersion - the
version of Claude Code that ran the suite - but nothing in the documented
schema identifies which commit or version of the plugin produced the
score.
That distinction matters more in CI than at your terminal. Two developers
can each get Δ +0.67 on the same eval suite while running two different
uncommitted edits to the plugin, and both runs are honestly reporting what
they measured: the plugin, whichever one was on disk, helped. Nothing in the
report says which plugin that was. A CI gate built on --threshold proves
the code sitting in that job's checkout passed the suite at that moment. It
does not by itself prove that the copy a teammate has installed, or the copy
that shipped to a marketplace, is the one that earned the score - that's a
separate claim, and it needs a separate mechanism to back it: a version
pinned in a manifest, and a hash checked against what's actually installed.
Where a lockfile fits, and where it stops
This is the piece skillfold is built for and nothing else in this post is:
proving that an installed skill's bytes are the exact ones a manifest
declared. skillfold check and install --frozen compare a sha256 hash of
what's on disk against the hash recorded in skillfold.lock
(src/install.ts), and fail loudly on any mismatch (docs/cli.md). That
answers "is this the revision we pinned," which a plugin eval score never
claims to answer on its own.
But a lockfile has the opposite blind spot from an eval score. Content
integrity says the bytes are unchanged; it says nothing about whether those
bytes do anything useful. A skill can pass every hash check in
skillfold check while its description never triggers on real phrasing, or
its instructions produce the wrong output every time - skillfold has no
way to run a session, invoke an agent, or grade a transcript, and isn't
trying to. Pinning and evaluating are answers to different questions -
"is this the code we agreed on" and "does that code work" - and a plugin
that's rigorously eval-gated in CI can still be silently swapped for an
unpinned edit the moment it leaves that job, with nothing in either tool
noticing on its own.