Claude Media
Claude CodeでNxのaffectedをフックから使う設計

Claude CodeでNxのaffectedをフックから使う設計

Nxのaffectedコマンドで変更影響範囲だけを検証する設計を、Claude CodeのStop・PostToolUse・FileChangedフックの使い分けとともに解説します。

Nxのaffectedが解決する問題

Nxのaffectedは、Gitの差分とNxのプロジェクトグラフを突き合わせ、変更の影響を受けるプロジェクトだけを特定するコマンドです。モノレポでプロジェクト数が増えると、コミットのたびに全プロジェクトをビルド・テストするのは非現実的になります。数十から数百のプロジェクトが並ぶ構成では、1つのライブラリを直しただけでも全件再実行に数十分かかることがあります。

nx affected -t <task> を実行すると、Nxは変更されたファイルからプロジェクトを特定し、そのプロジェクトに依存する他のプロジェクトまで辿ります。対象が絞り込めたら、指定したタスクをその範囲だけで実行します。影響範囲は nx graph --affected で可視化もできます。リモートキャッシュや分散実行と組み合わせるとさらに効果が上がりますが、ローカルのフック単位ではaffectedの絞り込みだけでも十分な速度改善になります。

nx affected -t test
nx graph --affected

Claude Codeでコード編集を任せる場面でも、この考え方はそのまま生きます。エージェントが1つのファイルを直しただけなのに、CIと同じ全件テストをローカルでも毎回走らせていては、セッションの待ち時間が長くなります。変更が及ぶ範囲だけを、セッションの区切りで自動検証するのがこの記事の設計です。モノレポの権限設計自体はClaude Codeモノレポ設計で扱っているので、CLAUDE.md階層と合わせて読むと構成の全体像がつかめます。

Claude CodeのどのフックでNx affectedを実行するか

Claude Codeのフックは、セッションのライフサイクル上の特定のタイミングでスクリプトやHTTPエンドポイントを呼び出す仕組みです。nx affectedをどこで実行するかは、PostToolUseStop のどちらを選ぶかでほぼ決まります。

PostToolUse はツール呼び出しが成功した直後に発火します。matcherEdit|Write に絞れば、Claudeがファイルを書き換えるたびにスクリプトが走ります。ただし1回の編集ごとに発火するため、10ファイルを連続で直すセッションでは同じ検証が10回走ってしまい、効率が悪くなります。

Stop はClaudeがそのターンの応答を終えたタイミングで1回だけ発火します。セッション中に何ファイル編集しても、応答が完了する瞬間に1回だけaffectedスコープを検証すればよいので、nx affectedとの相性はこちらの方が良好です。Stopdecision: "block" を返すことでClaudeの終了そのものを止められる点も重要です。テストが失敗している状態で応答を終わらせず、Claudeに差し戻せます。

FileChanged というファイル監視専用のフックもありますが、この用途には向きません。FileChangedmatcher はファイル名の完全一致リストとして解釈され、正規表現やワイルドカードによる包括的な監視ができません。監視対象を1ファイルずつ列挙する必要があるため、モノレポ全体のソース変更を拾う目的には向きません。

フックの設定・イベント・decision制御の全体像はClaude Code Hooks完全ガイドにまとめてあります。

Stopフックでaffectedスコープを検証する設定

.claude/settings.jsonStop フックを1つ登録します。Stop はマッチャーに対応していないイベントなので、matcher フィールドは書きません。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-nx-affected.sh",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

スクリプト側では、標準入力からJSONを受け取り cwdstop_hook_active を取り出します。stop_hook_activetrue のときは、直前の Stop フックによる差し戻しで既に1回検証が走っている状態なので、ここで無限ループを避けるためにそのまま終了します。

#!/bin/bash
input=$(cat)
cwd=$(jq -r '.cwd' <<<"$input")
active=$(jq -r '.stop_hook_active' <<<"$input")
 
if [[ "$active" == "true" ]]; then
  exit 0
fi
 
cd "$cwd" || exit 0
output=$(npx nx affected -t lint,test --base=main 2>&1)
status=$?
 
if [[ $status -ne 0 ]]; then
  reason=$(echo "$output" | tail -n 20)
  jq -n --arg r "$reason" \
    '{decision: "block", reason: ("affectedスコープの検証に失敗しました: " + $r)}'
fi
 
exit 0

nx affected はデフォルトで base をmainブランチ、head を現在のファイルシステムとして扱います。ローカルでの実行が前提のこのスクリプトでは、--base を明示しなくても動きますが、上の例ではmain以外をデフォルトブランチにしている構成でも動くよう明示しています。

PostToolUse・Stop・FileChangedの使い分け早見表

3つのフックはいずれもファイル変更を検知できますが、affectedスコープの検証に向くかどうかは用途によって変わります。

フック発火タイミングaffected検証への向き不向き
PostToolUse発火タイミングEdit/Write成功直後、1回の編集ごとaffected検証への向き不向き△ 編集回数だけ重複実行される
Stop発火タイミング応答完了時、セッションの区切りで1回affected検証への向き不向き◎ 累積した変更を1回でまとめて検証
FileChanged発火タイミング監視ファイルの変更検知(要ファイル名列挙)affected検証への向き不向き✕ モノレポ全体の包括監視には不向き

編集の合間に素早くフィードバックを返したいなら PostToolUse が有効な場面もあります。ただしその場合は、Nxのローカルキャッシュが効くぶん2回目以降の実行コストは下がるとはいえ、affectedの計算自体は毎回走る点に注意が必要です。セッション単位でまとめて検証したいなら Stop を選びます。

PostToolUse を選ぶ場合の設定は、matcherEdit|Write を指定するだけで、スクリプト自体はStopの例をそのまま流用できます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-nx-affected.sh"
          }
        ]
      }
    ]
  }
}

設定ファイル上の差は matcher の有無だけですが、セッション全体での実行回数は大きく変わります。10ファイルを連続で編集するセッションなら、Stop はセッションの終わりに1回、PostToolUse は編集のたびに合計10回スクリプトを起動します。Stop にはマッチャー指定がそもそも無いので、matcher を書いても無視される点も覚えておくと設定ミスを避けられます。

CIのbase/head設定とローカル実行の違い

CI環境では、nx affectedbasehead を明示的に指定する必要があります。GitHub Actionsなら nrwl/nx-set-shas アクションが、mainブランチ上で最後に成功したワークフローを見つけて NX_BASENX_HEAD を自動設定します。

- uses: actions/checkout@v7
  with:
    fetch-depth: 0
- run: npm ci
- uses: nrwl/nx-set-shas@v5
- run: npx nx affected -t lint test build

fetch-depth: 0 を指定しないと、比較対象となるコミット履歴が取得できず、affectedの計算自体が失敗します。Claude CodeのStopフックはこの複雑さを引き継ぎません。ローカルのワークツリーでセッションが動いている前提なら、base はデフォルトのmainブランチのままで足り、CIのようなSHA管理は不要です。

GitLab CIでのbase/head設定

GitLabにはGitHub Actionsのnrwl/nx-set-shasに相当する公式アクションがないため、CIの定義済み変数からNX_BASENX_HEADを組み立てます。

CI:
  script:
    - export NX_HEAD=$CI_COMMIT_SHA
    - export NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
    - npx nx affected -t lint test build

マージリクエストのパイプラインは差分のベースコミットと比較し、mainへのプッシュは1つ前のコミットと比較します。

CIのテスト戦略をSKILL.mdでチームに教える運用はClaude Codeモノレポのテスト戦略をSKILL.mdで教える手順で扱っています。Stopフックによるローカル検証と、CIのaffected実行を組み合わせる設計はここが起点になります。

よくあるつまずき

lock fileの変更で全プロジェクトが対象になっていないか

Nxはデフォルトで、パッケージマネージャーのlock fileが変わると全プロジェクトを影響対象としてマークします。Claude Codeが依存パッケージを追加・更新したセッションでは、affectedの絞り込みが効かず、通常の全件検証と同じ実行時間になります。nx.jsonpluginsConfig.@nx/js.projectsAffectedByDependencyUpdates"auto" にすると、lock file内で実際に依存が変わったプロジェクトだけに絞り込めます。

{
  "pluginsConfig": {
    "@nx/js": {
      "projectsAffectedByDependencyUpdates": "auto"
    }
  }
}

worktree内でCLAUDE_PROJECT_DIRとcwdを取り違えていないか

Claude Codeがセッション中にworktreeへ移動すると、${CLAUDE_PROJECT_DIR} は元のプロジェクトルートを指したままになります。一方でフック入力JSONの cwd フィールドはworktreeのルートに変わります。フックスクリプト自体は ${CLAUDE_PROJECT_DIR} から起動して構いませんが、nx affected を実行する対象ディレクトリは cwd から読み取る必要があります。前掲のスクリプトで cd "$cwd" を挟んでいるのはこのためです。

Gitを使わない構成で差分の基準を見失っていないか

Nxのaffectedはデフォルトでコミット履歴の差分に依存します。Gitを使わないワークフローでは --files フラグで変更ファイルを明示的に渡す必要があり、これを忘れるとNxは差分の基準を見失います。

Stopフックのタイムアウトがaffected実行に足りているか

設定例の timeout: 120 は、affectedの対象範囲が広いと不足することがあります。キャッシュが効いていない初回実行や、影響範囲が想定より広がった場合に備え、実運用ではプロジェクト規模に応じて余裕を持たせてください。フックのタイムアウトとexit codeの挙動の詳細はClaude Code Hooks実例カタログの各レシピも参考になります。

まとめ

Nxのaffectedは、Gitの差分とプロジェクトグラフから変更の影響範囲だけを特定し、そこにタスクを絞り込むコマンドです。Claude Codeでは、この絞り込みをセッションの区切りで自動検証する設計に落とし込めます。

PostToolUse は編集のたびに発火するため重複実行になりやすく、FileChanged はファイル名を列挙する監視方式のためモノレポ全体には不向きです。Stop フックで decision: "block" を返し、stop_hook_active で無限ループを防ぐ設計が、affectedスコープの検証には最も収まりが良い構成です。CIのbase/head管理をそのまま持ち込む必要はなく、ローカルのデフォルト値で足りる点も、Stopフックを選ぶ理由になります。

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