Claude CodeでVitestを回す — CLAUDE.mdに書くテストコマンドとwatch回避
Claude CodeにVitestを回させるとき、CLAUDE.mdへ書くのはvitest run・ファイル指定・カバレッジ閾値の3点です。watchモードで固まらない書き方とpermissions・hooksの設定例をまとめます。
Claude CodeにVitestのテストを回させるなら、CLAUDE.mdに書くコマンドは vitest run を軸にします。素の vitest は開発環境でwatchモードに入り、コマンドが終わらないからです。この記事では、CLAUDE.mdの書き方、ファイル単位の絞り込み、カバレッジ閾値、permissionsとhooksの組み合わせを順に見ます。
なぜ素のvitestをClaude Codeに回させると止まるのか
Vitestは、引数なしの vitest を開発環境ではwatchモードで起動します。CIや非対話のターミナルでは自動的にrunモードへ切り替わります。watchモードは変更を待ち続けるので、終了しないままセッションが待たされる危険があります。
公式CLIドキュメントの整理は次のとおりです。
| コマンド | 挙動 |
|---|---|
vitest | 挙動開発環境ではwatch、CIや非対話ターミナルではrun |
vitest run | 挙動watchなしで1回だけ実行 |
vitest watch / vitest dev | 挙動変更を監視して再実行(devはwatchの別名) |
vitest --run | 挙動watchモードを無効にするオプション |
vitest watch も、CIまたはstdinがTTYでない環境ではrunにフォールバックします。ただしClaude Codeのシェルがどちらに該当するかは、実行環境ごとに違います。判定を環境任せにせず、run を明示するのが安全です。
CLAUDE.mdに書くテストコマンド
CLAUDE.mdは、セッション開始時にコンテキストへ読み込まれる指示です。システムプロンプトではないので、厳密な遵守は保証されません。検証できる粒度で具体的に書くよう勧められています(例は「npm test を実行する」)。ここでは次のように書きます。
## テスト(Vitest)
- 全件: `npx vitest run`
- 単一ファイル: `npx vitest run src/foo.test.ts`
- 名前で絞る: `npx vitest run -t "ログイン失敗"`
- カバレッジ付き: `npx vitest run --coverage`
- 禁止: 引数なしの `vitest` と `vitest watch`(終了しないため)
- 変更したら、関連するテストを実行して出力を読み、失敗を直してから完了報告するポイントは3つあります。
- コマンドは完成形で書く。「テストを実行して」ではなく、コピーしてそのまま動く文字列にします。
- 禁止事項は1行で残す。「watchは使わない」と理由つきで書くと、後から読む人にも意図が伝わります。
- 完了条件も書く。実行して出力を読み、直すところまでを1つのループとして指示します。
package.json の scripts に "test": "vitest run" を置いてもよい構成です。その場合はCLAUDE.mdの案内を npm test に統一し、直接 vitest を呼ばせない形にすると、コマンドの揺れが減ります。同じ考え方をPythonで書いた例はClaude CodeにpytestのCLAUDE.md規約を教えるにあります。
テスト対象を絞ってトークンを節約する
全件実行は、出力が長くなりがちです。Vitestには絞り込みの手段が揃っています。
- ファイル名の部分一致:
vitest run foobarは、パスにfoobarを含むテストファイルだけを実行します。正規表現やglobは使えません(端末側で展開される場合を除く)。 - 行番号指定: Vitest 3以降は
vitest run basic/foo.test.ts:10の形でテストを指定できます。ファイル名は省略できず、foo:10は動きません。範囲指定(:10-25)も未対応です。 - テスト名の絞り込み:
-tまたは--testNamePatternに、フルネームへ一致する正規表現を渡します。 - 変更ファイルに関連するテスト:
vitest related src/index.tsは、指定ソースをカバーするテストだけを回します。静的importは追えますが、import(filepath)のような動的importは追えません。 - 変更差分だけ:
--changedは、変更されたファイルに影響を受けるテストを実行します。
CLAUDE.mdには「1ファイルの修正なら単一ファイル指定、広い変更のときだけ全件」と書いておくと、反復が速くなります。
- 1〜2ファイルの修正: そのファイルの `*.test.ts` だけ実行
- 共通モジュールの修正: `npx vitest related <変更したファイル> --run`
- 完了前に1回だけ全件: `npx vitest run`vitest related にも注意点があります。lint-stagedなどから呼ぶときは --run を付けるよう案内されています。Vitestはwatchが既定なので、付けないとコマンドが正常終了しないためです。Claude Codeに回させる場合も同じ理屈が当たります。
失敗が続くと出力が膨らむので、--bail 1 も選択肢です。指定した件数の失敗でテスト実行を止めます(既定は0で、止めません)。最初の失敗だけを読ませて直させる運用と相性がよい設定です。
レポーターとログで出力そのものを減らす
対象を絞っても、1件ごとの表示や console.log の出力が長ければトークンは減りません。出力側にも2つの手があります。
--reporter: 出力形式を選びます。選択肢はdefault、agent、minimal、blob、verbose、dot、json、tap、tap-flat、junit、tree、hanging-process、github-actionsです。--reporter dotと--reporter=dotはどちらも使えます。複数指定するときはオプションを繰り返します。--silent passed-only: テスト中のコンソール出力を抑え、失敗したテストのログだけを表示します。値なしの--silentは、テスト中のコンソール出力を抑えるオプションです。
CLIドキュメントには、各レポーターがどんな形式で出すかの説明がありません。agent や dot の出力内容もここでは確認できないので、名前から中身を決め打ちせず、手元で一度実行して長さと失敗時の詳細を見比べてください。
CLAUDE.mdには、確かめた組み合わせをそのまま書きます。
- 出力を抑えて全件: `npx vitest run --reporter=dot --silent passed-only`
- 詳細が必要なときだけ: `npx vitest run src/foo.test.ts`(reporter指定なし)カバレッジ閾値をコマンドに書く
カバレッジを条件にしたいときは、閾値をコマンドラインで渡せます。
npx vitest run --coverage \
--coverage.thresholds.lines=80 \
--coverage.thresholds.branches=70公式CLIドキュメントに載っている関連オプションは次のとおりです。
| オプション | 役割 |
|---|---|
--coverage.enabled / --coverage | 役割カバレッジ収集を有効にする(既定は無効) |
--coverage.provider | 役割計測ツールを選ぶ(v8 / istanbul / custom) |
--coverage.thresholds.lines など | 役割lines・functions・branches・statementsごとの閾値 |
--coverage.thresholds.100 | 役割全閾値を100にする近道 |
--coverage.thresholds.perFile | 役割ファイル単位で閾値を判定する |
--coverage.thresholds.autoUpdate | 役割現在値が閾値を上回るとき、設定ファイルの閾値を更新する |
--coverage.include / --coverage.exclude | 役割計測対象のglobを指定・除外する |
--coverage.thresholds.autoUpdate は、設定ファイルを書き換えるオプションです。Claude Codeにテストを回させる環境では、閾値が黙って引き上がる副作用を持つため、CLAUDE.mdには「autoUpdate は使わない」と書いておくのが穏当です。
閾値の値そのものはCLAUDE.mdに書き写さず、vitest.config.ts の設定に置く手もあります。値が二重管理になると、片方だけ古くなるからです。CLAUDE.mdには「閾値は設定ファイルにある。--coverage を付けて実行し、未達なら該当ファイルのテストを追加する」とだけ書きます。
permissionsでテストコマンドの確認を省く
CLAUDE.mdに書くのは「何を実行してほしいか」です。許可の確認を省くのは、設定ファイルの permissions.allow の役割になります。権限ルールはClaude Codeが強制するもので、CLAUDE.mdの記述は許可を変えません。
{
"permissions": {
"allow": [
"Bash(npx vitest run *)",
"Bash(npx vitest related *)",
"Bash(npm test *)"
],
"deny": [
"Bash(npx vitest watch *)",
"Bash(npx vitest dev *)"
]
}
}Bashルールの書き方には癖があります。
- 末尾の
*は、引数なしのコマンドにも一致します。Bash(npx vitest run *)はnpx vitest runそのものも許可します。ワイルドカードの前の空白はルールの一部です。 *はオプションも含めて何にでも一致します。ドキュメントの表では、Bash(npm run *)がnpm run test --watchにも一致する例が挙がっています。Bash(npm test *)も、npm test -- --watchを許してしまう余地があります。allowで許可した範囲にwatchが紛れ込む可能性は残ります。- denyルールは完全な壁ではありません。
Bash(rm *)を例に、絶対パス呼び出しやbash -c経由を止められないと説明しています。denyのwatch禁止は、Claudeが普通に書くコマンドを止める補助です。 - 一部のラッパーと環境変数は、取り除いて照合されます。
timeout 30 npm testやNODE_ENV=test npm testにもBash(npm test *)が効きます。一方、npxは取り除かれないラッパーに含まれます。ルールはnpx vitest runのように書く必要があります。 - 複合コマンドは各部分を個別に判定します。
npm test && other-cmdのうち、許可に含まれない部分は確認に回ります。
つまり、watchを止める仕組みは層で持ちます。CLAUDE.mdで「使わない」と伝え、package.json のscriptを vitest run に固定し、denyで補助します。
hooksで編集のたびに関連テストを回す
「テストを回して」と毎回頼む代わりに、hooksで機械的に走らせる手もあります。CLAUDE.mdの指示は、ドキュメントの言葉で言えばコンテキストであって強制設定ではありません。確実に実行したい処理はhookにする、というのが公式の整理です。
PostToolUseとStopでテストを回す設計はテストフレームワークを問わず共通なので、Jest・pytestなど複数の例はClaude Codeのhooksでテストを自動実行する方法にまとまっています。ここではVitest固有の部分(related、--run、パスの形、関連テストが0件のとき)だけを扱います。
公式のPrettier例と同じ形で、Edit|Write の後に関連テストを実行する設定は次のようになります。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx vitest related --run --passWithNoTests"
}
]
}
]
}
}--passWithNoTests は「テストが見つからなければ成功扱いにする」オプションです。.md のようにテストと無関係なファイルを編集したとき、関連テストが0件でもhookが失敗しません。
書くときに確かめたい点が2つあります。
- パスの形。
vitest relatedの公式説明は、ファイルをルートからの相対で渡す前提です。hookが渡すパスが絶対パスの場合の扱いは、CLIドキュメントに記載がありません。手元のプロジェクトで一度動かして確認してください。 - 失敗の伝わり方。上の設定のままでは、テストが落ちても内容がClaudeに届く保証はありません。stderrがClaudeに見えるのは、
PostToolUsehookがexit 2で終わったときです。exit 0のときのstderrはデバッグログにしか残らず、Claudeは読めません。exit 2はツールがすでに走ったあとの出力なので、編集を取り消すのではなく、失敗内容を次の判断材料として渡す形になります。
hookが decision: "block" と reason をJSONで返す方法もあります。PostToolUse では既定で、ターンがそこで終わり、理由は画面に警告行として出ます。continueOnBlock: true を設定すると、理由がClaudeに返ってターンが続きます。ただしこの設定項目が載っているのはpromptタイプのhookの設定表で、commandタイプのhookに同じ項目があるかは確認できていません。テスト失敗を直させる用途では、まずexit 2でstderrに失敗内容を出す形から試すのが手堅い選択肢です。
Stopで「完了前に全テストを通す」ゲートを置く選択肢もあります。agent hookの例に「全ユニットテストが通ることを確認してから停止を許可する」があります。ただしagent hookは実験的機能と明記されており、本番運用にはcommand hookが勧められています。
つまずきやすい点
| 症状 | 見直す点 |
|---|---|
| コマンドが終わらない | 見直す点引数なしの vitest を呼んでいないか。CLAUDE.mdと package.json の test を vitest run に揃える |
指示したのに vitest を素で呼ぶ | 見直す点/context の「Memory files」でCLAUDE.mdが読み込まれているか確認する。指示が曖昧・矛盾していないかも見る |
| 毎回許可を聞かれる | 見直す点permissions.allow のルールが npx vitest run * のように、実際のコマンド文字列と一致しているか |
foo:10 形式が動かない | 見直す点ファイル名は拡張子つきの完全な名前で書く(foo.test.ts:10) |
| カバレッジが出ない | 見直す点--coverage を付けているか。coverage.enabled の既定は無効 |
| CLAUDE.mdが長くなった | 見直す点200行以下を目安に、テスト関連だけをパススコープ付きルールへ分ける |
CLAUDE.mdが「読み込まれているのに守られない」場合の切り分けは、Claude Codeのメモリー機能のドキュメントにあるトラブルシューティングが手順化しています。読み込み確認、置き場所、指示の具体性、矛盾の4点です。
他のテスト系の記事との使い分け
E2Eをhookで回す構成は、Claude CodeでPlaywrightのE2Eテストをhookで自動実行するが扱っています。Vitestは単体テスト側の話で、ファイル単位で高速に反復できる点が強みです。go test を軸にした検証ループなら、Claude CodeでGoアプリを開発する手順が参考になります。
要するに、Vitestを任せるコツは「終わるコマンドを完成形で書く」に尽きます。vitest run を基本に、絞り込みとカバレッジをオプションで足し、許可はpermissions、強制はhooksに分担させます。