Claude Media
Claude CodeでVitestを回す — CLAUDE.mdに書くテストコマンドとwatch回避

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. コマンドは完成形で書く。「テストを実行して」ではなく、コピーしてそのまま動く文字列にします。
  2. 禁止事項は1行で残す。「watchは使わない」と理由つきで書くと、後から読む人にも意図が伝わります。
  3. 完了条件も書く。実行して出力を読み、直すところまでを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ファイルの修正なら単一ファイル指定、広い変更のときだけ全件」と書いておくと、反復が速くなります。AngularのテストをMCPサーバー経由で回す構成はClaude CodeでAngular CLIのMCPサーバーを使う手順にあります。

- 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 です。複数指定するときはオプションを繰り返します。
  • --silent passed-only: 通ったテストの console.log を抑え、失敗したテストのログだけを表示します。値なしの --silent は、テスト中のコンソール出力をすべて抑えます。

レポーターには、AIコーディングエージェント向けのものがあります。agent は minimal の別名で、失敗したテストとエラーメッセージだけを出し、通ったテストのログと末尾のサマリーも出しません。reporters を設定していない状態でVitestがAIコーディングエージェントの中で動いていると判断すると、minimal が自動で選ばれます。

自動選択には落とし穴があります。--reporter=dot のように自分で指定すると、既定のレポーター一覧が置き換わるため、minimal の自動選択は働きません。出力を絞りたいなら、dot と --silent passed-only の組み合わせか、minimal の明示指定のどちらかに決めておきます。

数字

同じ失敗1件(3テスト中1件が失敗)の出力量

  • 既定のレポーター

    約44行

    1,493バイト。通ったテストの console.log と ✓ の行も出る

  • dot + --silent passed-only

    約35行

    1,113バイト。失敗の詳細とサマリーは残る

Vitest 3.2.4で確認。色なしで、stdoutとstderrを合算し、サマリーも含めて数えた値です

dot と --silent passed-only を組み合わせると、通ったテストは ·、失敗は x の1文字になります。失敗の詳細は残ります。末尾には Failed Tests の見出しとコードフレーム、サマリー(Test Files / Tests / Duration)も出ます。次は失敗の詳細部分だけを抜粋した出力です。

npx vitest run --reporter=dot --silent passed-only
 RUN  v3.2.4 <プロジェクトのパス>
 
·x
stdout | src/sum.test.ts > ログイン失敗
debug: ログイン
·
 FAIL  src/sum.test.ts > ログイン失敗
AssertionError: expected 2 to be 3 // Object.is equality
 
- Expected
+ Received
 
- 3
+ 2
 
 ❯ src/sum.test.ts:4:70

Vitest 3.2.4では、--reporter=agent と --reporter=minimal が起動エラーで止まります。メッセージは Failed to load custom Reporter from agent(minimal)です。Cannot find package 'agent' と続くので、組み込みの名前として解決されず、同名のパッケージを探しに行っています。reportersのドキュメントには agent と minimal が載っていますが、3.2.4には入っていません。古いVitestのプロジェクトにCLAUDE.mdの例をそのまま貼ると、テストが1件も走りません。

CLAUDE.mdには、npx vitest --version で確かめたうえで動いた組み合わせをそのまま書きます。

- 出力を抑えて全件: `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を止める3つの層

  • CLAUDE.md

    「引数なしの vitest は使わない」と伝えます。強制力はありません。

  • package.json

    test スクリプトを vitest run に固定し、呼び出しを1本にします。

  • permissions.deny

    vitest watch と vitest dev を補助的に止めます。

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 は「テストが見つからなければ成功扱いにする」オプションです。Vitest 3.2.4で README.md のようなテストと無関係なファイルを渡すと、vitest related README.md --run は No test files found, exiting with code 0 と表示して終了コード0で終わりました。related では、このオプションがなくても失敗しません。

一方、vitest run nomatch のようにファイル名フィルタが1件も一致しないと、No test files found, exiting with code 1 で終了コード1になります。--passWithNoTests を付けるとこれが0に変わります。hookのコマンドを related から run <フィルタ> に書き換えるときの保険として残しておく使い方です。

CLIドキュメントは related に渡すファイルをルートからの相対パスとしています。3.2.4では vitest related "$PWD/src/sum.ts" --run のように絶対パスを渡しても、対応する src/sum.test.ts のテストが実行されました。hookが渡す tool_input.file_path は絶対パスなので、jq で相対パスに直す手間は要りません。

失敗の伝わり方は、hookの終了コードで決まります。

くらべる

PostToolUse hookでテストが落ちたとき

Claudeに届かない

exit 0、または2以外

exit 0ではstderrはデバッグログにだけ残ります。2以外の非0では、画面に非ブロッキングのエラー通知が出て、stderrの1行目だけが表示されます。上の設定ではテスト失敗がexit 2にならず(xargs は123で終わります)、Claudeには失敗内容が渡りません。

Claudeに届く

exit 2

stderrがClaudeに見えます。ツールはすでに走ったあとなので、編集は取り消されず、失敗内容が次の判断材料になります。

テスト失敗を直させる用途なら、vitest の出力をstderrに回して exit 2 で終える小さなシェルスクリプトを呼ぶ形から始めると、Claudeに失敗が届きます。

hookが ok: false と reason を返す書き方もあります。これはpromptタイプとagentタイプのhookの応答形式です。PostToolUse では既定でターンがそこで終わり、理由は画面に警告行として出ます。promptタイプで continueOnBlock: true を設定すると、理由がClaudeに返ってターンが続きます。agentタイプには continueOnBlock の項目がなく、ok: false はこの設定を true にしたときと同じ扱いで、PostToolUse ではターンが続きます。

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は単体テスト側の話で、ファイル単位で高速に反復できる点が強みです。Vue 3ではvue-tscとVitestを組み合わせた検証ループがあります。go test を軸にした検証ループなら、Claude CodeでGoアプリを開発する手順が参考になります。

Vitestを任せるときは、終わるコマンドを完成形で書き、出力を絞るレポーターはバージョンを確かめてから決めます。許可はpermissions、確実に走らせたい処理はhooksの担当です。Jestを使うプロジェクトのメジャーアップグレードは、Claude CodeでJest 30へアップグレードする手順で扱っています。

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