🚀 はじめに
Claude Code の Skill(スキル)やプラグインを自作していると、必ずこの疑問にぶつかります。
このスキル、本当に効いてる?
description を書き換えてみたけれど呼ばれているのか分からない。プロンプトを調整したけれど、良くなったのか運が良かっただけなのか分からない。モデルが上がったときに壊れていないかも分からない。
Claude Code v2.1.269 で追加された claude plugin eval は、この「分からない」をスコアに変えるコマンドです。
# プラグイン(スキル)ルートで実行するだけ
claude plugin eval .
CASE WITH W/OUT Δ RUNS COST NOTES
first-case 1.00 0.33 +0.67 6 $0.41
1 case(s) · mean Δ +0.67 · 74s · $0.41
Report: /Users/you/my-plugin/evals/results/2026-09-10T17-02-11-482Z/report.html
WITH がプラグインあり、W/OUT がプラグインなしのスコア、そして Δ がプラグインの貢献分です。この Δ こそが、この機能の一番おいしいところです。
この記事では、実際に自作スキルのリポジトリを題材に、eval スイートの作り方から CI でのゲート化、コスト設計までを一通り追いかけます。
本記事のスコアテーブルやレポート例は公式ドキュメントの例です。eval の実行とジャッジグレーダーは実際のモデル呼び出しで課金が発生するため、この記事では課金が発生しない範囲(マニフェスト検証・スイートの雛形生成・プラグインの解決とロードまで)を手元の v2.1.269 で実機確認し、それ以外は公式ドキュメントに基づいて記述しています。
📝 3行まとめ
-
v2.1.269 で
claude plugin evalが追加。プラグインやスキルをテストケース群にかけて、スコア付き・再現可能な結果(JSON + HTML レポート)を得られる -
目玉は「プラグインなしベースライン」との差分
Δ。スキルが無くても同じ結果なら、そのスキルはスコアに貢献していない、という事実が可視化される -
--thresholdの終了コードで CI ゲート化できる。ただし実行環境はあなたのマシンとは別物(他のスキルもCLAUDE.mdも無い使い捨て環境)で、しかも実モデル呼び出しの課金が発生する点は押さえておきたい
🤔 なぜスキルに eval が必要なのか
スキルは実態としては Markdown で書かれたプロンプトです。つまり、普通のコードのようにユニットテストで守れません。にもかかわらず、次のような壊れ方を普通にします。
| 壊れ方 | 症状 |
|---|---|
| そもそも呼ばれない |
description が曖昧で、ユーザーの自然な言い回しでトリガーされない |
| 呼ばれるが従わない | 手順を書いているのに、出力形式やファイル出力先が守られない |
| 回帰する | スキルを少し直したら、別のケースが壊れる |
| モデル更新で変わる | 新しいモデルで挙動が変わり、以前の前提が崩れる |
| そもそも要らなかった | スキルが無くても Claude が同じ品質で答えていた |
最後の「そもそも要らなかった」は特に厄介で、スキル無しの比較対象がないと永遠に気づけません。claude plugin eval はこれを標準で測ります。
⚙️ eval 実行の仕組み
構造はシンプルです。
- eval スイートはプラグイン内の
evals/ディレクトリに置く -
1 ケース = 1 ディレクトリで、
prompt.md(送るプロンプト)とgraders/(採点方法)を持つ - 各実行では、プラグインだけをロードした隔離された非対話セッションが新規に立ち上がり、空のワークスペースでプロンプトを処理する
- 実行後、各グレーダーが「最終応答・トランスクリプト・生成されたファイル」を見て PASS / FAIL を判定する
そして重要なのがここです。
- エージェントの 1 回の実行はノイズが大きいため、各ケースはデフォルトで 3 回実行される
- 実行のスコアは「(重み付き)合格グレーダーの割合」、ケースのスコアはその平均
- ケースは、そのスコアが
--threshold(デフォルト 1.0)を満たせば合格
さらにデフォルトでは with アーム(プラグインあり)と without アーム(プラグインなし)の 2 本立てで走ります。つまり 1 ケース = 3 実行 × 2 アーム = 6 実行です。ここはコストに直結するので後述します。
実行環境には「あなたの環境」が無い
ここは結果の解釈に直結するので、明示しておきます。各実行は使い捨てのホームディレクトリ・作業ディレクトリ・Claude Code 設定を与えられ、テスト対象のエージェントはその中で claude -p の子プロセスとして起動し、対象のプラグインだけをロードします。
-
個人・プロジェクトレベルのものは何もロードされません。 ユーザー設定、フック、
CLAUDE.md、MCP サーバー、他のインストール済みプラグイン、メモリ、スキルは存在せず、サンドボックス上のプロジェクトスコープの.claude/や.mcp.jsonも読まれません -
シェル環境もほとんど遮断されます。 許可リストの変数(
PATH、ロケール、プロキシと証明書設定、モデルプロバイダーを選択・認証する変数、大半のANTHROPIC_*とCLAUDE_CODE_*)とEVAL_*だけが実行に届きます。プラグインに値を渡したいときはEVAL_*としてエクスポートします - 管理設定(managed settings)の制限は実行内にも効きます。 管理下のマシンとそうでないマシンでは結果が変わりえます
- Artifact ツールはオフです。 アーティファクトを公開するスキルは、その手前で生成するものまでしか採点できません
- ケース定義はエージェントから隠されています。 実行は eval ディレクトリを読めないため、プロンプト・グレーダー・兄弟ケースを覗いてカンニングすることはできません
このうち「他のスキルが 1 つも存在しない」という性質は、後半の「eval が測れないこと」に直結します。
🧪 自作スキルを eval にかけてみる
ここからは、筆者が公開している qiita-claude-skills のような「.claude/skills/ にスキルを並べただけのリポジトリ」を題材にします。
qiita-claude-skills/
└── .claude/
└── skills/
├── qiita/
│ └── SKILL.md
└── qiita-claude-code-release-notes/
└── SKILL.md
1. eval の対象は「プラグイン」である
claude plugin eval のターゲットは パス / プラグイン名 / plugin@marketplace の ID です。そして対象になるのは、plugin.json または .claude-plugin/plugin.json のマニフェストを持つプラグインディレクトリ(または skills-directory プラグイン)です。
つまり SKILL.md が 1 枚あるだけのディレクトリは、そのままでは eval のターゲットになりません。マニフェストを 1 枚置くのが最初の一歩です。
プラグインルートに SKILL.md があり、skills/ サブディレクトリも skills マニフェストフィールドも無い場合は「単一スキルプラグイン」として扱われるので、マニフェストは最小限で済みます。
mkdir -p .claude/skills/qiita/.claude-plugin
cat > .claude/skills/qiita/.claude-plugin/plugin.json <<'EOF'
{
"name": "qiita",
"description": "Qiita投稿用の記事を作成するスキル",
"version": "0.1.0"
}
EOF
構文とスキーマは claude plugin validate で確認できます(こちらはモデルを呼ばないので無料です)。
claude plugin validate .claude/skills/qiita
Validating plugin manifest: /path/to/.claude/skills/qiita/.claude-plugin/plugin.json
‼ Found 1 warning:
> author: No author information provided. Consider adding author details for plugin attribution
√ Validation passed with warnings
author を足せば警告も消えます。挙動ではなく構文を見るのが validate、挙動を見るのが eval という住み分けです。
2. スイートを作る
推奨は対話型の init です。Claude がプラグインを読み、「良い結果とは何か」を質問し、トリガーすべき/すべきでないプロンプトを提案し、グレーダーを設計し、1 回パイロット実行してからファイルを書いてくれます。
cd .claude/skills/qiita
claude plugin eval init
初回は Trust this plugin directory? と聞かれるので y で答えます。
一方「まず中身を自分の目で確認したい」「CI で雛形だけ欲しい」という場合は --bare で空のテンプレートを作れます。こちらはモデルを呼ばず、ファイルを置くだけです。実際に手元で叩いた結果がこちら。
claude plugin eval init --bare writes-article
Created evals/writes-article/prompt.md and evals/writes-article/graders/criteria.md
evals/writes-article/
├── prompt.md
└── graders/
└── criteria.md
生成された prompt.md:
---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---
TODO: describe what the agent should do
生成された graders/criteria.md:
---
type: llm
weight: 1
---
TODO: describe what a successful response looks like
この TODO を埋めるのが作業の中心です。
3. プロンプトを書く
prompt.md の本文は「ユーザーが実際に打ちそうな言い回し」にします。ここを「qiita スキルを使って記事を書いて」のようにスキル名を明示してしまうと、トリガー精度のテストにならない点に注意してください。
---
max_turns: 15
timeout_seconds: 600
allowed_tools: [Read, Glob, Grep, Write, Skill]
tags: [smoke]
---
Claude Code の Hooks について、Qiita に投稿する記事を書いてください。
各実行は空のワークスペースから始まるので、タスクに必要な材料はプロンプト自体に入れるか、後述の case.yaml の context でワークスペースを用意します。本文中の @path はファイル添付には展開されないので、ファイルを読ませたいなら allowed_tools でツールを渡します。
4. グレーダーを書く
「答えが正しかったか」と「そこへスキル経由で到達したか」の 2 系統を必ず入れるのがコツです。
① スキルが発火したか(tool_used)
---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?qiita"'
---
plugin-name:skill-name という名前空間付きの形式にもマッチします。
② 記事ファイルが生成されたか(file_exists)
---
type: file_exists
path: "**/*_qiita_*.md"
---
実行中に作成されたファイルだけがカウントされます。
③ 構成ルールを守っているか(regex)
SKILL.md に「3行まとめ」を含めよと書いてあるなら、それを機械的に検査できます。
---
type: regex
pattern: "3行まとめ"
target: files
---
④ 記事として成立しているか(llm)
---
type: llm
weight: 2
---
PASS if 記事が日本語で書かれており、「はじめに」「3行まとめ」「まとめ」「参考リンク」に相当する節をすべて含み、コードブロックが 1 つ以上ある。
FAIL if 英語で書かれている、節が欠けている、または本文が箇条書きの羅列だけで解説文がない。
llm グレーダーはジャッジモデルが 3 回中 2 回 PASS に投票すれば合格という多数決方式です。
📊 グレーダーは 6 種類
カスタムコードグレーダーはありません。用意された 6 種から選びます。
| タイプ | 主なオプション | 合格条件 | 課金 |
|---|---|---|---|
regex |
pattern / flags / match / target
|
JavaScript 正規表現がターゲットにマッチ(match: not_contains で不在、match: "count:N" で回数指定) |
無料 |
tool_used |
tool / input_match / min / max
|
対象ツールの呼び出し回数が min〜max の範囲。min: 0, max: 0 で「呼ばれていないこと」を主張 |
無料 |
tool_order |
before / after
|
両方のツールが呼ばれ、before が after より先 |
無料 |
file_exists |
path / exists
|
実行中に作成されたファイルが glob にマッチ(exists: false で不在) |
無料 |
llm |
criteria / focus
|
ジャッジモデルが 3 回中 2 回 PASS に投票 | 課金 |
baseline |
baseline_file / criteria
|
参照トランスクリプトと同等に基準を満たしていると判定 | 課金 |
安定したスコアを得るためのポイントは以下です。
- 生成ファイルのような長い出力は
regexで検査する。毎回同じ判定になる -
llmは短い出力に使い、ルーブリックは「PASS if / FAIL if」の具体条件で書く - 各ケースに「結果に対するグレーダー」と「到達経路に対するグレーダー(
tool_used/tool_order)」を 1 つずつ -
llmのジャッジはデフォルトで小さく速いモデル。ニュアンスのある判定が必要なら--judge-model sonnet
⚖️ 最重要:Δ(アブレーション)の読み方
単独のスコアが高いだけでは、プラグインが役に立った証拠になりません。プラグイン無しでも Claude が同じ結果を出せるかもしれないからです。
そこで各ケースはデフォルトで with / without の 2 アームで走り、その差が Δ として出ます。
-
WITH 1.00/W/OUT 1.00/Δ 0.00→ スキルが理由で合格したわけではない -
WITH 1.00/W/OUT 0.33/Δ +0.67→ スキルが効いている -
Δがマイナス → スキルが邪魔をしている可能性(あるいはジャッジがフォーマットに引きずられている)
ここで公平性のための仕掛けがあります。「スキルが呼ばれたか」のようなチェックはプラグイン無しでは絶対に合格できないため、そのままカウントすると without 側が 0 に寄って Δ が水増しされます。そのため、
-
tool: Skillのtool_usedグレーダー -
arm: with-onlyを指定したグレーダー
は両アームのスコアから除外され、with アームでは「plugin-fired indicator(発火したかどうかの指標)」として報告されます。レポート上もバッジが付きます。
そして、最初に遭遇しがちな典型パターンがこれです。
tool_used: Skillが FAIL していてΔがほぼゼロ → Claude が自然な言い回しでスキルを選んでいない
この場合に直すべきは本文ではなく、SKILL.md の description です。直して claude plugin eval . を回し直して比較する、というのが基本の改善ループになります。
なお、グレーダーを調整している段階など Δ が要らない場面では --ablation none を付けると with アームのみになり、コストが半分になります(テーブルは WITH / W/OUT / Δ ではなく SCORE / PASS% 表示に変わります)。
# 1 ケースだけ、1 実行、片アームで素早く回す
claude plugin eval . --case writes-article --runs 1 --ablation none
1 実行はノイズが大きいので、最終判断はデフォルトの 3 実行に戻してから行いましょう。また --ablation none では除外処理が働かないため、同じスイートでもモードによって絶対スコアが変わります。
🥊 eval が測れないこと:他スキルとの競合
この節は、本記事に寄せられたコメントでの指摘を受けて追記しました。指摘の内容は公式ドキュメントの記述および v2.1.269 の実機挙動と一致することを確認しています。
前述の通り、eval の実行環境には他のインストール済みプラグインもスキルも存在しません。つまり tool_used: Skill が測っているのは、
候補が自作スキル 1 本しか無い世界での発火率
です。実運用でよくある「マーケットプレイスを何本も入れた結果、description が近い別スキルに持っていかれる」という失敗は、素の構成では原理的に検出できません。Δ が正で tool_used: Skill も通っているのに、本番では別スキルが呼ばれる、という状態は普通にありえます。
description のチューニングは、本来「単独で拾われるか」ではなく「競合に勝てるか」で評価したいところです。
競合相手を同時にロードできる
結論から言うと、ケースの plugins フィールドに複数のプラグインディレクトリを並べれば、同じ実行に同時ロードできます。
公式ドキュメントの plugins の説明は「テスト対象のプラグインディレクトリ。ケースディレクトリからの相対パス」で、載っている例は自動検出が効かないときの plugins: ["../.."] だけです。複数指定の例は示されていませんが、配列の全要素がそれぞれ解決され、子セッションに個別の --plugin-dir として渡されます。
説明文をわざとかぶせたダミープラグインを、自分のプラグイン配下に置きます。
qiita/ # テスト対象のプラグイン
├── .claude-plugin/plugin.json
├── SKILL.md
├── rivals/
│ └── rival/ # 競合ダミー(eval ディレクトリの外に置く)
│ ├── .claude-plugin/plugin.json
│ └── SKILL.md # description をわざと似せる
└── evals/
└── writes-article/
├── prompt.md
└── graders/
ケースの prompt.md で両方を指定します。
---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
plugins: ["../..", "../../rivals/rival"]
---
Claude Code の Hooks について、Qiita に投稿する記事を書いてください。
実行すると、ロードされたプラグインが 2 つとも表示されます。
Plugin under test: "qiita" version "0.1.0" at "/path/to/qiita"
Plugin under test: "rival" version "0.1.0" at "/path/to/qiita/rivals/rival"
Ablation: 2 arms × 1 case (2 runs)
evals/results/<timestamp>/aggregate-result.json にも両方が記録されます。
"plugins": [
{ "name": "qiita", "version": "0.1.0", "path": "/path/to/qiita" },
{ "name": "rival", "version": "0.1.0", "path": "/path/to/qiita/rivals/rival" }
]
この状態で tool_used: Skill(自分のスキル名でマッチ)を測れば、競合がいる中での発火率が取れます。ダミー側にも tool_used グレーダーを min: 0, max: 0 で置けば、「相手に取られていないこと」を明示的に主張できます。
3 つの制約
ただし、素直に使えるわけではありません。実機で確認した制約が 3 つあります。
① 封じ込めルートの外は指定できない
plugins の各要素は「封じ込めルート」の内側に解決される必要があります。~/.claude/plugins/ にインストール済みの本物の競合プラグインを指す、といった使い方はできません。
case "writes-article": plugins entry "../../../outside-rival" resolves to /path/to/outside-rival,
outside the containment root /path/to/qiita (the enclosing plugin for a target inside one you
control, else the directory you ran 'claude plugin eval' against).
Only plugins under it can be loaded from case.yaml.
つまり競合ダミーは、自分のリポジトリ内に自前で持つ必要があります。配布物に混ざらないよう、パッケージング時の除外は別途必要です。
② eval ディレクトリの中には置けない
evals/ 配下にダミーを置いたケースは、ロード自体は通るものの実行時にエラーになりました。
error: case "writes-article": a plugins entry names the case directory, its graders or mocks,
or a directory covering them — a plugin shipped with a case must sit in its own subdirectory
eval スイートの外(上の例では rivals/)に、独立したサブディレクトリとして置くのが正解です。
③ Δ は競合を分離してくれない
ここが一番重要です。plugins に並べたものはすべて「Plugin under test」として扱われます。したがって without アームでは両方まとめて剥がされます。
| with アーム | without アーム | |
|---|---|---|
| ロードされるもの | 自作スキル + 競合ダミー | 何もなし |
つまり Δ が答えているのは「自作スキル+競合がいる状態 vs 何も無い状態」であって、「競合に勝てたか」ではありません。競合テストで信頼できるのは、Δ ではなく with アームの tool_used: Skill インジケータ(何回中何回で自分が呼ばれたか)です。判断材料としては、こちらを --runs を多めにして見るのが実用的です。
複数指定は公式ドキュメントに明記された用途ではありません(plugins が配列であること自体は記載されていますが、競合プラグインを同時ロードする使い方の保証はありません)。本節は v2.1.269 の実機挙動にもとづく記述で、将来のバージョンで変わる可能性があります。
おまけ:課金ゼロでロードだけ確認する
「どのプラグインがロードされたか」だけを確かめたいなら、ケースの model にわざと存在しないモデル名を入れる手が使えます。子セッションが起動直後に失敗するため、推論は 1 回も走らず COST は $0.00 のまま、プラグインの解決結果とアームの構成だけが出力されます。
---
model: definitely-not-a-real-model
---
writes-article run 1/1 [with]: score 0.00 $0.00 error: exit 1: There's an issue with the
selected model (definitely-not-a-real-model). It may not exist or you may not have access to it.
当然スコアは 0 になるので、採点用途には使えません。plugins のパス指定や封じ込めルールを詰める段階でだけ使える確認手段です。
🗂 フィクスチャとモック
「空のワークスペースから始まる」制約は、case.yaml の context ブロックで補えます。
schema_version: "1.1"
name: changelog-from-diff
tags: [smoke]
context:
scaffold_script: fixture.sh # 事前セットアップ用の Bash(--scaffold 指定時のみ実行)
add_dirs: [resources] # 実行中に読ませたいフィクスチャ
# history_file: prev.jsonl # 途中からの会話を再現したい場合
MCP を呼ぶスキルの場合は、実サーバーを起動せずに 1 ツール = 1 Markdown のモックで答えさせられます。
---
expect:
title: string
priority: [low, medium, high]
---
Created issue #4821: {{input.title}}
evals/mocks/<server>/<tool>.md に置けばスイート全体、ケース配下の mocks/ に置けばそのケース専用です。expect: に違反する呼び出しはスコア 0 で中断されるため、「プラグインがサーバーに何を渡したか」まで主張できます。
🤖 CI でスコアをゲートする
--threshold を下回ると終了コード 1 で落ちるので、そのまま CI のゲートになります。
name: plugin-eval
on:
pull_request:
paths:
- '.claude/skills/**'
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g @anthropic-ai/claude-code
- name: Run plugin evals
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude plugin eval .claude/skills/qiita \
--trust-plugin \
--json results.json \
--threshold 0.8 \
--model claude-sonnet-5 \
--judge-model claude-haiku-4-5 \
--no-publish \
--max-cost-usd 5
- uses: actions/upload-artifact@v4
if: always()
with:
name: eval-results
path: results.json
ポイントは 4 つです。
-
--trust-plugin: 初回の信頼プロンプトで CI が止まらないようにする(--jsonを付けた時点で対話できないため、これが無いと終了コード 1 で拒否されます) -
--modelと--judge-modelを固定: スコアを時系列で比較可能にする -
--no-publish: HTML レポートをローカルに留める -
--max-cost-usd: 予算の上限。各実行の起動前にチェックされる
終了コードの意味は以下の通りです。
| 終了コード | 意味 |
|---|---|
0 |
全ケースが --threshold 以上。全ケースファイルがロード成功 |
1 |
閾値未満、ケースのロード失敗、ケースが見つからない、実行を開始できない、未信頼ディレクトリで --trust-plugin 無し、オプション不正 |
2 |
部分実行(--max-cost-usd 到達、または認証情報の拒否)。results.json に partial: true と理由が書かれる |
130 |
中断(部分結果は書き出される) |
143 |
終了(CI タイムアウトなど) |
HTML レポートの書き込みや公開の失敗は終了コードを変えません。なお claude plugin eval init は質問のためにターミナルが必要なので、CI では --bare を使います。
evals/results/ には実行のたびに成果物が書き出されるので、.gitignore に追加しておきましょう。
evals/results/
💰 コスト設計
**eval 実行もジャッジグレーダーも、あなたのアカウントでの実モデル呼び出しです。**プランの使用量または API 請求にカウントされます。
ざっくりした規模感は以下です。
エージェント実行数 ≒ ケース数 × runs × 2(with / without)
ジャッジ呼び出し数 ≒ 実行数 × (llm / baseline グレーダー数) × 3
抑えるための定石:
- 毎コミットで回す軽量スイートは、ジャッジを呼ばないグレーダー(
regex/tool_used/tool_order/file_exists)だけで構成する -
Δが要らない場面は--ablation noneで半額 - イテレーション中は
--runs 1 --case <name> -
--max-cost-usdでハードリミット。上限に当たると有料グレーダーはスキップされ、無料グレーダーだけで採点されます(partial: trueが付いた結果はトレンド集計から除外するのが安全) - 並列化は
-j/--concurrency(1〜8)。ただし各実行は自分の認証情報で動く本物の子プロセスなので、同じレート制限を共有します
🔐 セキュリティ上の注意
claude plugin eval は プラグインをあなたの権限で、あなたのマシン上で実行します。実行時のサンドボックスは悪意あるプラグインができることを制限しますが、保証ではありません。信頼できるプラグインだけを評価してください。
特に、次のフラグは「明示的に危険側へ倒す」ものです。
| フラグ | 何が起きるか |
|---|---|
--trust-plugin |
初回の信頼プロンプトをスキップ(CI 向け。--dangerously-skip-permissions と同種の判断) |
--scaffold |
ケースの scaffold_script(作者提供の Bash)をあなたとして実行 |
--allow-tools |
Bash / Write / Edit / WebFetch / mcp__* など、読み取り専用セットを超えるツールを解禁 |
--allow-real-servers / --mocks off
|
プラグインの実 MCP サーバーを起動。OS サンドボックスの外で動く |
フックや実サーバーはエージェントのサンドボックス外で動き、グレーダーが読むファイルに触れる可能性があります。コンテナや CI ランナーなど隔離環境で回さない限り、そのスコアは参考値として扱うのが安全です。また「バンドルされたスイートが合格すること」はセキュリティ検証ではありません。
🧯 ハマりどころ
| 症状 | 対処 |
|---|---|
plugin eval is currently in early access |
ビルドが古い。claude update してから新しいセッションで再実行 |
plugin eval is currently unavailable |
サーバー側でオフにされている。手元では戻せないので claude update して後で再試行 |
is not a trusted plugin directory, and this run cannot stop to ask you about it |
初回実行かつ非対話(--json 指定含む)。一度ターミナルで実行して承認するか --trust-plugin
|
No eval cases found |
eval ディレクトリ配下に <case>/prompt.md か case.yaml が無い。あるいは --case / --tag のフィルタ不一致 |
grader "..." is still the blank init template |
--bare で作った雛形の TODO: 行が残っている。具体的な合格条件に書き換える |
outside the containment root |
plugins のパスが封じ込めルートの外。対象プラグイン配下(または eval を実行したディレクトリ配下)に置く |
a plugin shipped with a case must sit in its own subdirectory |
plugins が eval ディレクトリやケースディレクトリを指している。スイートの外の独立したサブディレクトリに移す |
| スコアが落ちる原因が見えない |
--json を外してローカル実行すると、実行ごとの進捗行とグレーダー行が出る |
evals/ が別ツールで使われている場合は、plugin.json に "experimental": { "evals": "quality/evals" } を書くか、--eval-dir quality/evals を渡して置き場所を変えられます。
✅ まとめ
claude plugin eval は、これまで「なんとなく良くなった気がする」で運用されていたスキル/プラグイン開発に、スコアという共通言語を持ち込むコマンドです。
特に価値があるのは、単なる合否ではなく プラグインなしベースラインとの差分 Δ を標準で測ってくれる点です。「スキルを消しても結果が変わらない」という耳の痛い事実まで含めて可視化されます。
今日からやれるアクションは次の 3 つです。
-
claude --versionで v2.1.269 以降であることを確認する(古ければclaude update) - 自作スキルに
.claude-plugin/plugin.jsonを置き、claude plugin validateを通す(ここまで無料) -
claude plugin eval initで 1 ケースだけ作り、--runs 1 --ablation noneで安く回して感触を掴む
そのうえで、tool_used: Skill が落ちていないか、Δ が正になっているかを見ながら description を磨いていく。この 1 周を経験すると、スキル開発の解像度がかなり変わります。
ただし、そこで得られる発火率は「競合が 1 つも無い世界」の数字です。実運用でスキルを何本も同居させているなら、plugins に競合ダミーを並べて tool_used: Skill を測る一手間まで入れると、description の評価軸が「単独で拾われるか」から「競合に勝てるか」に変わります。
🔗 参考リンク
- evals でプラグインをテストする - Claude Code Docs
- プラグインリファレンス - Claude Code Docs
- スキル - Claude Code Docs
- Claude Code CHANGELOG
- Claude Code v2.1.263 - v2.1.269 リリースノートまとめ
🗒 更新履歴
-
2026-09-13: コメントでのご指摘を受けて「eval が測れないこと:他スキルとの競合」を追記し、実行環境の分離に関する説明(ユーザー設定・
CLAUDE.md・他プラグイン・MCP サーバーがロードされないこと)を具体化しました。pluginsへの複数指定、封じ込めルート、Δが競合を分離しない点は v2.1.269 の実機で確認しています。