Claude Media
claude plugin evalの使い方 — 現実的なケースの書き方とCI組み込み

claude plugin evalの使い方 — 現実的なケースの書き方とCI組み込み

claude plugin evalでプラグインの挙動を検証する実践ガイド。プロンプトとgraderの設計、no-pluginとの差分測定、CIゲート化までを扱います。

claude plugin evalは、Claude Codeのプラグインを実際に動かして採点するコマンドです。コマンド自体の起動方法・主要フラグ・終了コードはClaude Code v2.1.269のリリースノートで扱っています。本記事はその先、どんなケースを書けば意味のある検証になるかに絞ります。現実的なプロンプトの作り方、graderの設計、no-pluginベースラインとの差分の読み方、CIへの組み込み方です。

claude plugin evalとは

claude plugin evalは、プラグインのevalスイートを実行して結果を採点するコマンドです。ケースは現実的なプロンプト1つと、grader(採点基準)1つ以上の組み合わせで、graderは応答のregexマッチ・特定ツールが呼ばれたか・第二のモデルによるルーブリック判定のいずれかです。用途は3つあります。プラグインが正しい結果へClaudeを導く確率の測定、プラグイン変更時や新モデル登場時のリグレッション検知、プラグインなしとの比較で「何が効いているか」を見ることです。

実行にはClaude Code v2.1.269以降が要ります。claude --versionで確認し、古ければclaude updateで上げます。gitが入っている環境ではgit 2.31以降も必要です。古いgitではフックや資格情報ヘルパーを無効化する環境変数(GIT_CONFIG_COUNT)が効かず、実行前に拒否されます。この検証自体はv2.1.283で追加されたもので、それ以前のバージョンはgitのバージョンを確認せずに実行していました。

対象はplugin.jsonか.claude-plugin/plugin.jsonを持つプラグインディレクトリ、またはskills-directoryプラグインです。evalの実行もjudge graderの呼び出しも、実際のモデル課金になります。プランの使用量かAPI課金として計上されるため、CIで頻繁に回す前にコストを見積もっておく必要があります。

evalスイートの構成を決める

evalスイートはプラグイン内のevals/ディレクトリに置きます。ケース1つがサブディレクトリ1つで、中にprompt.md(プロンプト本体)とgraders/(採点基準)を持ちます。作り方は2通りです。

くらべる

ケースの作り方は2通り

対話で作る

claude plugin eval init

プラグイン直下で実行すると対話セッションが開き、プラグインを読んでどんなプロンプトが妥当かを尋ねます。graderを設計し、動作を試したうえでファイル一式を書き出します。ケースをまとめて用意したいときの既定の方法で、対話ができる端末が要ります。

雛形だけ

init --bare

prompt.mdとcriteria.mdの雛形を1ケース分だけ書き出します(中身はTODO)。モデルを呼ばず、端末の対話も要らないので、CIでも使えます。書き方が決まっているケースを手で足すときに向きます。

--bareは、プラグイン(またはスキル)のフォルダの中で実行するのが前提です。v2.1.287で、プラグインではない空のフォルダから実行すると次のように拒否されました。

claude plugin eval init --bare first-case
Error: <作業フォルダ> is not a plugin or skill folder — run `claude plugin eval init` from the plugin's root folder, or pass --eval-dir to scaffold here on purpose.

エラー文のとおり、--eval-dirを渡せばプラグイン外でも雛形だけを書き出せます。

.claude-plugin/plugin.jsonだけを置いた最小のフォルダで同じコマンドを実行すると、次の2ファイルができました。

Created evals/first-case/prompt.md and evals/first-case/graders/criteria.md

prompt.mdのfrontmatterには、max_turns: 10とallowed_tools: [Read, Glob, Grep, Skill]が入っています。本文はTODO: describe what the agent should doの1行だけです。

criteria.mdのfrontmatterはtype: llmとweight: 1です。本文はTODO: describe what a successful response looks likeの1行です。どちらも空ではなく、既定値とTODOが入っています。このcriteria.mdはtype: llmなので、TODOを具体的なPASS/FAIL条件に書き換えるまでは、judgeを呼ぶ有料のgraderが1つ置かれた状態になります。

evals/が別ツールに使われている場合は置き場所を変えられます。plugin.jsonに"experimental": { "evals": "quality/evals" }を書くか、実行時に--eval-dir quality/evalsを渡します。

現実的なプロンプトをprompt.mdに書く

prompt.mdのfrontmatterには、そのケースの実行条件を書きます。本文はClaudeがそのまま受け取るプロンプトです。

---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---
 
Write me a commit message for this change: I renamed getUser to fetchUser and updated the three call sites.

スキル名を書かずに、ユーザーが打つであろう自然な依頼文で書きます。「〇〇スキルを使って」ではなく「このコミットメッセージを書いて」と頼み、Claudeがそのスキルを自分の判断で選ぶかどうかを見ます。@pathのようなファイル添付記法は展開されないので、Claudeにファイルを読ませたいならallowed_toolsにツールを許可します。

各runは空のワークスペースから始まります。ファイルやgitリポジトリが要るケースは、case.yamlにcontext.scaffold_scriptでBashスクリプトを指定します(実行は--scaffoldを渡したときだけ)。会話の続きから始めたいケースはcontext.history_fileに.jsonlのトランスクリプトを渡すと、プロンプトが「その続きの発言」として扱われます。max_turnsは既定10、timeout_secondsは既定300秒で、時間のかかるタスクは緩めに設定しないと打ち切りがそのままスコアを下げます。

runはユーザー設定・project-level設定を一切読み込みません。個人のCLAUDE.md・フック・MCPサーバー・他の導入済みプラグイン・メモリー・スキルはすべて不在です。プラグインが何か前提を必要とするなら、プラグイン自体に同梱するか、scaffold_scriptで作るか、EVAL_*で始まる環境変数として渡すしかありません。ケースがなぜか失敗するときは、まず「普段の環境にだけあるもの」に依存していないかを疑うのが早道です。

graderを設計する — 何を測るか、どう安定させるか

graderには6種類あり、性質が2つに分かれます。regex・tool_used・tool_order・file_existsはトランスクリプトとファイルから機械的に判定するのでコストがかかりません。llmとbaselineはjudgeモデルを呼ぶので追加コストが発生します。

grader何を見るかコスト
regex何を見るか応答やトランスクリプトの正規表現一致コストなし
tool_used何を見るか特定ツールが呼ばれた回数・入力コストなし
tool_order何を見るか2つのツール呼び出しの前後関係コストなし
file_exists何を見るかrun中に作られたファイルの有無コストなし
llm何を見るかjudgeモデルによるルーブリック判定(3票中2票でPASS)コストあり
baseline何を見るか参照トランスクリプトとの相対比較コストあり

llm graderはモデルに聞く以上、runごとに答えがぶれます。読ませる文章が長いほどぶれも増えます。安定させるコツは3つあります。

  • 長い出力(生成ファイルなど)はファイル全体をregexで機械的にチェックします。llm graderは短い応答用に、PASS/FAIL条件を具体的に書いたルーブリックだけに使います
  • 1ケースに「結果そのもの」を見るgrader(最終応答やファイル)と「そこに至る手順」を見るgrader(tool_usedやtool_order)を1つずつ置きます
  • ビルドやテストの成否を確かめたいときは、Claudeに実行させて結果をファイルへ書かせます。そのファイルを採点しつつ、tool_usedのinput_matchで実際にコマンドが走ったことも確認します

プラグインがMCPツールを呼ぶ場合、実サービスなしでも検証できます。evals/mocks/<server>/<tool>.mdにツールごとのモックを置くと、runは実サーバーを起動せずモックから応答します。モックにはexpect:で入力の形を指定でき、違反するとrunはスコア0で中断されます。呼び出し内容そのものを採点したいときはtarget: mock_callsを指定したgraderを使います。この仕組みはプラグインに同梱されたMCPサーバーを配布しているプラグインで特に効きます。

no-pluginベースラインとの差分(Δ)を読む

1回のスコアだけでは、プラグインが本当に効いているのか分かりません。Claudeがプラグインなしでも同じ結果を出せるかもしれないからです。そこで各ケースは既定で2アーム実行されます。プラグインありのwith-armと、プラグインなしのwithout-arm(no-pluginベースライン)です。両者のスコア差がΔで、プラスならプラグインが貢献したことになります。両方とも1.0なら、そのケースはプラグインが無くても通っています。

2アーム実行では、一部のgraderが両アームで公平に比較できません。「スキルが呼ばれたか」を見るgraderはプラグインなしでは原理的に通らないため、そのままではΔを水増ししてしまいます。Claude Codeは次のgraderを自動的にスコア計算から除外し、with-armでは合否の参考表示だけに使います。

  • toolがSkillのtool_used grader
  • プラグイン自身が宣言するMCPサーバーへのtarget: mock_callsのregexやfocus: mock_callsのllm
  • arm: with-onlyを指定したgrader

「スキルを絶対に呼ばせない」ことを確認したいケースでは、逆にarm: bothを指定して両アームで強制的に採点させ、min: 0とmax: 0を組み合わせます。比較が要らない反復作業では--ablation noneでwith-armだけを実行でき、コストは半分になります。ただし除外の仕組みごと働かなくなるため、同じ--thresholdでも合否が変わることがあります。

Δが小さいときは、次の順に切り分けます。

手順

Δが小さいときの切り分け

  1. 1

    tool_used: Skillのgraderを見る

    失敗しているなら、Claudeが自然な言い回しでスキルを選んでいません。スキルのdescriptionをプロンプトの表現に寄せて、同じスイートを再実行します。

  2. 2

    通っているのにΔがマイナスならjudgeを疑う

    小さなjudgeモデルは、正しい応答でも整形がルーブリックの想定と違うだけで不合格にすることがあります。--judge-model sonnetのような強いモデルで再実行し、ルーブリックも絞り込みます。

  3. 3

    変更は3回の既定で確かめる

    1回だけの結果はぶれます。直したと思ったら、--runsを指定せずにもう一度回して判断します。

回す前にコストを見積もる

evalは1ケースあたりの実行回数が多く、思ったより高くつきます。ケースは既定で3回(--runsで変更)走り、同じ数だけ、プラグインなしのベースラインのrunも加わります。judgeモデルの呼び出しは、llmやbaselineのgraderごとに、runごとに3回の短い判定が加わります。

数字

5ケースのスイートを既定設定で回したときのagent run数

  • 既定(with-without・3 runs)

    30回

    プラグインあり15回 + なし15回

  • --ablation none

    15回

    プラグインありだけ。コストは半分

  • 1ケースを--runs 1 --ablation none

    1回

    調整中の確認用

ケース数 × runs で数えた目安。judgeの呼び出しはこれに上乗せされます

調整のたびにスイート全体を回さず、直したケースだけを絞って1回だけ走らせるのが現実的です。次のように--caseで絞ります。

claude plugin eval . --case first-case --runs 1 --ablation none

--max-cost-usdで見積もりの上限を置く方法は、次の節で扱います。

CIにゲートとして組み込む

CIで回すときは、結果をJSONで残し終了コードでビルドを失敗させます。信頼プロンプトで止まらないよう--trust-pluginを付け、モデルは固定し、コストには上限を置きます。

claude plugin eval . \
  --trust-plugin \
  --json results.json \
  --threshold 0.8 \
  --model claude-sonnet-5 \
  --judge-model claude-haiku-4-5 \
  --no-publish \
  --max-cost-usd 20

--thresholdの既定は1.0で、1つでも下回るケースがあると終了コード1で落ちます。コスト上限に達して途中で止まったときや、最初のrunの前後で資格情報が拒否されたときは終了コード2になり、results.jsonにはpartial: trueと理由が付きます。

--modelをCIで固定するのは、モデルの既定更新をプラグインの退行と誤認しないためです。--judge-modelも同じ理由で固定します。終了コードの意味や課金上限の扱いはClaude Code v2.1.269のリリースノートにまとめてあるので、そちらを参照してください。ここで押さえておきたいのは運用面の3点です。CI環境にはANTHROPIC_API_KEYなどの資格情報が要ること。--trust-pluginを付けないとチェックアウトディレクトリが未信頼のまま拒否されること。CIでの雛形作成は対話プロンプトを出せないのでclaude plugin eval init --bare <name>を使うことです。トレンドを追うグラフには、コスト上限で打ち切られたpartial: trueの結果や、judgeが省略されたskippedPaidGradersのrunを混ぜないようにします。

skill-creator evalsとどう違うか

似た名前の仕組みがClaude Codeにはもう1つあります。1つのスキルを対話セッション内で素早く反復するための、skill-creatorプラグインのeval機能です(手順はこちら)。両者は別物です。claude plugin evalはプラグイン全体を対象にした公式コマンドで、evals/ディレクトリのケース形式を使い、CIでのゲート化を前提にしています。skill-creatorのevalはevals/evals.jsonという独自形式を使い、開発中のセッション内でその場のスキルを比較する用途に寄っています。どちらもプラグインなしとの比較でスコアを出す発想は共通ですが、片方のケースファイルをもう片方が読むことはありません。すでにskill-creatorでスキルを仕上げていても、配布するプラグインとしてCIでの継続的な検証をしたいなら、claude plugin evalのスイートを別に用意することになります。ファイルの構文・スキーマだけを確かめたいなら、挙動を採点するclaude plugin evalではなくclaude plugin validateが対象です。

よくあるつまずき

  • 全ケースが0点になる: 作成されたパスの一覧を返すfilesを指定していて、本当はファイルの中身を見たい設定に多い症状です。{ source: file, path: <path> }に直します。file_existsはrun中に新規作成されたファイルしか数えないので、既存ファイルをClaudeが編集しただけのケースではtool_usedでEditを確認します
  • regexがトレースにマッチしない: targetの既定はlast_messageで、トレース全体ではありません。traceを対象にする場合はJSONの1行1メッセージなので、引用符は\"として現れます。正規表現の構文はJavaScript準拠で、大小文字を無視したいならflags: iを使い、インラインの(?i)は使えません
  • usage-limitやrate-limitで途中から全部0点になる: 途中でプランの使用上限やAPIのレート制限に達すると、それ以降のrunはエラーで終わりほぼ0点になります。スイート自体はpartial扱いにならず終わるため、見かけ上のリグレッションに見えます。NOTES列やJSONのerrorにある制限メッセージを確認してから、上限リセット後に再実行します
  • Bashやサンドボックスの前提を見落とす: --allow-toolsでBashを許可すると、runはOSレベルのサンドボックス内で動きます。ネイティブのWindowsにはサンドボックスのバックエンドが無いためWSL2で実行し、Linuxでは事前にbubblewrapとsocatを用意します

まとめ

claude plugin evalでプラグインを検証する要は、ケースの質にあります。スキル名を伏せた自然なプロンプトを書き、結果を見るgraderと手順を見るgraderを1つずつ組み合わせ、no-pluginベースラインとのΔで「プラグインが本当に効いているか」を確認します。CIに組み込むときはモデルとjudgeモデルを固定し、コスト上限を置いて、--trust-pluginで信頼プロンプトを回避します。1つのスキルを素早く試したいだけならskill-creatorのevalで十分です。配布するプラグイン全体を継続的に検証したいなら、claude plugin evalのスイートを別に用意する価値があります。プラグインの配布・マーケットプレイスの仕組みはClaude Codeプラグインマーケットプレイスの必須化と自動更新設定にまとめました。公式マーケットプレイスに並ぶプラグインの傾向はClaude Code公式マーケットプレイスのプラグイン一覧で扱っています。

この記事を共有:XはてブLinkedIn