Claude Media
Claude CodeでLighthouse CIを自動実行 — Hooksでパフォーマンス回帰を防ぐ

Claude CodeでLighthouse CIを自動実行 — Hooksでパフォーマンス回帰を防ぐ

Claude CodeのHooksでLighthouse CIを自動実行し、性能回帰があるセッションの終了をブロックする設計をStop/PostToolUse/FileChangedの使い分けで解説します。

Lighthouse CIは、GoogleChrome組織が公開しているパフォーマンス回帰検知用のOSSツール群です。Claude CodeのHooksと組み合わせると、コード変更のたびにLighthouseの計測を挟み、スコアが悪化したセッションの終了を止める仕組みが作れます。この記事ではStop・PostToolUse・FileChangedの3イベントを比較し、実際に動くsettings.jsonとスクリプトの例を示します。

Lighthouse CIとは何か

Lighthouse CIとは、Lighthouseの計測結果を継続的に実行・保存・取得・アサートするためのOSSツール群です。GoogleChrome組織がGitHub上で公開しており、ライセンスはApache-2.0です。

主な機能は次の6つです。

  • PRごとにLighthouseレポートを添付する
  • アクセシビリティ・SEO・オフライン対応・パフォーマンスのベストプラクティスの回帰を防ぐ
  • パフォーマンス指標とLighthouseスコアの推移を記録する
  • スクリプトや画像にパフォーマンス予算を設定して維持する
  • ばらつきを減らすためLighthouseを複数回実行する
  • 2つのバージョンを比較し、リソース単位の改善・劣化を見つける

READMEのQuick Startでは、GitHub Actions上で次のコマンドを実行する例が示されています。

npm install && npm install -g @lhci/cli@0.15.x
npm run build
lhci autorun

lhci autorunが収集・アサート・アップロードまでを一括で行うエントリーポイントです。Claude CodeのHooksからこのコマンドを呼び出せば、AIがコードを変更した直後にパフォーマンス計測を挟めます。

Hooksで実行するタイミングをどう選ぶか

Lighthouse CIをHooksに組み込むときの最初の判断は、どのイベントで発火させるかです。候補はPostToolUseStopFileChangedの3つで、それぞれ得意な場面が違います。

イベント発火タイミングブロック可否向いている使い方
PostToolUse発火タイミングツール呼び出し直後ブロック可否できない(stderrをClaudeに見せるのみ)向いている使い方特定ファイル編集への即時フィードバック
Stop発火タイミングメインエージェントの応答完了時ブロック可否できる(decision: "block"で継続を強制)向いている使い方セッション終了前に予算超過を検知してゲートする
FileChanged発火タイミング監視対象ファイルがディスク上で変わったときブロック可否できない向いている使い方npm run buildなど外部コマンドが書き換えるビルド成果物の検知

PostToolUsematcher: "Edit|Write"で絞れますが、外部プロセス由来の書き換えを拾えない制約があります(詳細は後述します)。npm run build経由で更新されるビルド成果物を監視したいならFileChangedを使います。

一方で「セッションを終える前に必ずLighthouseの予算を満たしているか確認する」というゲート的な使い方にはStopが向いています。decision: "block"でClaudeの停止を防げるのはStopSubagentStopだけで、パフォーマンス回帰検知の自動実行設計としてはこの制御フィールドが使える点がStop固有の強みです。次の節ではこのStopパターンを実装します。

Stopフックで性能予算チェックをブロック条件にする

Stopフックは、Claudeが応答を終えるたびに発火します。ここにlhci autorunを仕込み、失敗したときだけdecision: "block"を返せば、性能予算を満たすまでセッションを終わらせない設計になります。

まずフックスクリプトを.claude/hooks/lighthouse-stop-check.shとして保存します。

#!/bin/bash
# Stop: Lighthouse CIの予算チェックを完了条件にする
input=$(cat)
stop_hook_active=$(echo "$input" | jq -r '.stop_hook_active')
 
# 既にこのフックの継続でループ中なら、これ以上ブロックしない
if [ "$stop_hook_active" = "true" ]; then
  exit 0
fi
 
output=$(npx --yes @lhci/cli@0.15.x autorun 2>&1)
exit_code=$?
 
if [ $exit_code -ne 0 ]; then
  reason=$(echo "$output" | tail -n 20)
  jq -nc --arg reason "Lighthouse CIの予算を満たしていません。$reason" \
    '{decision: "block", reason: $reason}'
  exit 0
fi
 
exit 0

stop_hook_activeのチェックが必須です。Claude Codeは同一のStopフックが8回連続でブロックすると、強制的にターンを終了します。このチェックを省くと、フックが自分自身の継続を検知し続け、8回の上限で打ち切られます。この上限はCLAUDE_CODE_STOP_HOOK_BLOCK_CAP環境変数で変更でき、0を指定すると上限自体を無効化できます。

スクリプトを実行可能にし、.claude/settings.jsonに登録します。

chmod +x .claude/hooks/lighthouse-stop-check.sh
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lighthouse-stop-check.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

args: []を付けているのはexec formで実行するためです。パス置換に${CLAUDE_PROJECT_DIR}を使う場合は、シェルを経由しないexec formのほうが引用符の扱いに悩まされません。commandhttpmcp_toolタイプのタイムアウトはデフォルトで600秒(10分)確保されています。ビルドを含む計測がこれを超えるようなら、timeoutフィールドに秒数を明示して延長します。

PostToolUseとFileChangedで代替する設計

セッション全体ではなく、編集のたびに素早くフィードバックしたい場合はPostToolUseが候補になります。matcher: "Edit|Write"を指定すれば、Claudeがファイルを編集した直後に発火します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lighthouse-quick-check.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

ただしPostToolUseには2つの制約があります。1つは、ツールがすでに実行済みのためdecision: "block"を返してもツール呼び出し自体は取り消せない点です。もう1つは、Bashコマンドや外部プロセスが同じファイルを書き換えたときは発火しない点です。

npm run buildの出力先ディレクトリを監視したいなら、書き込み元を問わず反応するFileChangedのほうが適切です。FileChangedは監視対象をmatcherに指定しますが、他のイベントのように正規表現として評価されることはなく、|区切りのリテラルなファイル名の並びとして扱われます。ビルド成果物の代表ファイルを1つ指定する運用に向いています。

同期実行と非同期実行をどう使い分けるか

Lighthouse CIはLighthouseを複数回実行してばらつきを減らす設計のため、環境によっては実行完了までに時間がかかります。この待ち時間をClaudeに負わせるかどうかが、asyncを付けるかどうかの分かれ目です。

観点同期実行(Stopasyncなし)非同期実行(async: true)
Claudeの待機同期実行(Stop、asyncなし)発生する(計測完了までブロック)非同期実行(async: true)発生しない(バックグラウンドで実行)
結果でブロックできるか同期実行(Stop、asyncなし)できる(decision: "block"が効く)非同期実行(async: true)できない(decisionなどの制御フィールドは無視される)
結果が届くタイミング同期実行(Stop、asyncなし)その場で非同期実行(async: true)次のターン(additionalContext経由)
向く使い方同期実行(Stop、asyncなし)終了前の必須ゲート非同期実行(async: true)編集ごとの参考情報

非同期実行を選ぶとdecisionpermissionDecisioncontinueはすべて無効になります。バックグラウンドで動き始めたあとはtimeoutも強制されなくなるため、lhci autorunが想定より長引いても途中で打ち切られる心配がありません。Lighthouse CIの結果でセッションの完了を左右したいなら同期実行のStop、単に「今の変更でスコアがどう動いたか」を後から確認したいだけなら非同期のPostToolUseという住み分けになります。

非対話モード(-p)でCIに組み込むときの注意

Lighthouse CIのQuick StartはGitHub Actions上での実行を想定した例です。Claude Code側でこの自動実行をCIパイプラインに乗せるなら、-pフラグの非対話モードが起点になります。

Claude Codeはワークスペースの信頼状態をセッション種別ごとに扱います。対話セッションでは、フォルダの信頼ダイアログを承認するまで、~/.claude/settings.jsonを含むすべての設定ファイルのHooksが保留されます。一方-pフラグやAgent SDK経由のセッションでは、この信頼ダイアログ自体が表示されず、フォルダは常に信頼済み扱いになります。

つまり、レビューしていないリポジトリに対してclaude -pを実行すると、そのリポジトリの.claude/settings.jsonに書かれたHooksがそのまま動きます。CIランナーが外部から取得したコードに対してclaude -pを実行する構成では、事前に.claude/配下の設定を確認するか、--bareフラグで起動するか、--settings '{"disableAllHooks": true}'でHooksを明示的に無効化してから走らせる選択肢があります。

よくあるつまずき

Lighthouse CIをHooksに組み込むとき、次の4点でつまずきやすいところがあります。

Stopをはじめ多くのイベントでブロックを起こす唯一のexit codeは2で、exit code 1では止まりません。JSON出力なしでexit code 1を返しても、Claude Codeはそれを非ブロッキングのエラーとして扱い、そのまま進めます。decision: "block"のJSONを返すか、exit 2を明示する必要があります。

8回ブロックの上限に注意。前述のとおりstop_hook_activeを確認しないと、Stopフックは8回連続のブロックで強制終了させられます。Lighthouse CIの修正に8回以上の往復が必要なケースを想定するなら、CLAUDE_CODE_STOP_HOOK_BLOCK_CAPを引き上げる選択肢もあります。

コマンドフックはフルユーザー権限で動作します。lhci autorunはビルド成果物の読み取りだけでなく任意のNode.jsプロセスを起動します。公式ドキュメントも、コマンドフックがユーザーアカウントと同じ権限でファイルの変更・削除・アクセスができる点を明記しています。公式が挙げるHooks全般のセキュリティ対策は次の5つです。

  • 入力データを無条件に信頼せず検証・サニタイズする
  • シェル変数は"$VAR"のように必ず引用符で囲む
  • ファイルパスに..が含まれていないか確認しパストラバーサルを防ぐ
  • スクリプトの参照は絶対パスにする(exec formでは${CLAUDE_PROJECT_DIR}をそのまま使い、shell formでは二重引用符で囲む)
  • .env.git/、鍵ファイルなど機微なファイルへのアクセスを避ける

exec formとshell formの取り違えargsを省略するとcommandはシェル経由で解釈されるため、${CLAUDE_PROJECT_DIR}のようなプレースホルダーは二重引用符で囲む必要があります。args: []を付けたexec formなら、シェルを介さず文字列がそのまま渡るので引用符の心配がありません。

Ctrl-Cで中断したセッションはゲートをすり抜けるStopフックはClaudeが応答を完了するたびに発火しますが、タスクの完了時に限らず、ユーザーによる割り込みでは発火しません。作業を急いでCtrl-Cでセッションを打ち切ると、Lighthouse CIの予算チェックを経ないままコード変更だけが残ります。APIエラーで応答が止まったときはStopFailureが代わりに発火するため、その系統の異常も拾いたいならStopFailureフック用のハンドラを別途用意します。

まとめ

Lighthouse CIをClaude CodeのHooksに組み込む設計は、PostToolUseStopFileChangedのどれを選ぶかで性格が変わります。編集直後の速報が欲しいならPostToolUse、ビルド成果物の変化を捉えたいならFileChanged、性能予算を満たすまでセッションを終わらせたくないならStopdecision: "block"を組み合わせます。Stopを使う場合はstop_hook_activeのチェックを忘れずに入れ、lhci autorunの実行時間がタイムアウトに収まるかを一度手元で計測しておくと、実運用に持ち込みやすくなります。

Hooksの全イベントと設定の基本はClaude Code Hooks完全ガイド、他の実用レシピはClaude Code Hooks実例カタログにまとめています。完了条件を強制する設計そのものをもう少し広く知りたい場合は、Agent Teamsでの品質ゲートを扱ったAgent Teams Hooksで強制する品質ゲートの仕組みも参考になります。計測結果を外部ダッシュボードへ転送したい場合は、コマンドフックの代わりにClaude CodeのHooksをHTTPエンドポイントで受ける設計も選べます。

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