powered by TechFeed
表示モード
ハウツー

AIコーディングエージェントへの「指示」は無視される — Claude Code・Cursor・Codexのフック機能で確実に動かす方法

11月2日、Antoine van der Leeが「Claude Code, Cursor, and Codex Hooks Explained」と題した記事を公開した。この記事では、Claude Code・Cursor・Codexの3ツールにおけるエージェントフックの仕組みと実装方法について詳しく紹介されている。

11月2日、Antoine van der Leeが「Claude Code, Cursor, and Codex Hooks Explained」と題した記事を公開した。この記事では、Claude Code・Cursor・Codexの3ツールにおけるエージェントフックの仕組みと実装方法について詳しく紹介されている。


「指示」では不十分な理由

AIコーディングエージェントに AGENTS.md やグローバル指示でルールを書いても、エージェントがそれを無視したり忘れたりすることは珍しくない。コンテキストに含まれた指示はあくまで「読むもの」であり、モデルがどう扱うかは保証されない。

この問題は、GitHub CopilotやClaude Code、Cursorといったエージェント型AIツールが普及するにつれて顕在化してきた。自律的に複数のツールを呼び出しながらタスクをこなすエージェントは、長いセッションの中でコンテキストウィンドウが圧迫され、当初の指示を「実質的に忘れる」ことが起きやすい。チームのコーディング規約やプロジェクト固有の制約を確実に守らせたい場面では、指示ベースのアプローチには構造的な限界がある。

フック(hook)はこの問題を根本から回避する。 エージェントではなく、ツール自体がスクリプトを決まったタイミングで実行する。エージェントに読ませるのではなく、ツールが強制的に動かす仕組みだ。

指示(AGENTS.md等) フック
実行主体 モデル(エージェント) ツール本体
信頼性 忘れる可能性あり 毎回必ず実行
用途 意図・好みの伝達 絶対に守らせたいルール

フックの構造

ウェブフック(webhook)を知っているなら発想は同じだ。「何かが起きたとき、定義したスクリプトが走る」。フックは4つの要素で構成される。

(画像が表示されない場合の補足)フックは「イベント→トリガー→フックスクリプト→アクション」の4ステップで動作する。エージェント(モデル)はこのパイプラインに関与しない。

  • イベント: シェルコマンド実行前・ターン終了・セッション開始など、エージェントの動作の中で定義された瞬間
  • トリガー: イベントへのフィルタ。シェルコマンドだけを対象にするなど
  • フックスクリプト: 実際に走るシェルスクリプト。イベントの詳細をJSON(stdin)で受け取る
  • アクション: コマンドをブロックする・内容を変更する・コンテキストを追加するなど

「実行前フックはブロックや変更ができる、実行後フックは取り消せない」という区別を覚えておくと実装の判断が速くなる。なお、Claude Codeのフック仕様については公式ドキュメントも参照されたい。


3ツールの設定比較

Claude Code・Cursor・Codexはいずれもフックをサポートするが、設定ファイルの場所やイベント名が異なる。

Claude Code Cursor Codex
ユーザー設定 ~/.claude/settings.json ~/.cursor/hooks.json ~/.codex/hooks.json
プロジェクト設定 .claude/settings.json .cursor/hooks.json .codex/hooks.json
ツール実行前 PreToolUse preToolUse / beforeShellExecution PreToolUse
ツール実行後 PostToolUse postToolUse / afterFileEdit PostToolUse
ブロック方法 exit code 2 または permissionDecision: "deny" exit code 2 または permission: "deny" exit code 2 または permissionDecision: "deny"
フック失敗時 onFailure が "block" でなければ続行 failClosed が true でなければ続行 フック失敗として報告

シェルコマンドを対象とするツール名も Claude Code・Codexでは Bash、Cursorでは Shell と異なる点も注意が必要だ。


実例:Xcodeビルドログをフィルタするフック

著者が最初に作ったのが、xcodebuild の生ログをフィルタするフックだ。生ログは長大で、エージェントのコンテキストを無駄に消費する(トークン削減については別記事で数値が紹介されている)。xcsift というツールでエラー・警告・テスト結果だけに絞るのが目的だ。

以下が ~/.agents/hooks/require-xcsift.sh として保存するスクリプト(chmod +x で実行権限を付与):

#!/bin/bash
input=$(cat)
command=$(jq -r '.tool_input.command // empty' <<< "$input")

# xcsift未適用のビルド・テストコマンドにのみ作用する
if [[ "$command" == *xcodebuild* \
   && "$command" =~ (^|[[:space:]])(build|test)([[:space:]]|$) \
   && "$command" != *xcsift* ]]; then
  message="Run it again as: set -o pipefail; $command 2>&1 | xcsift -f toon -w"

  # CursorはJSON応答、Claude Code/CodexはExitコード2+stderr
  if jq -e 'has("cursor_version")' <<< "$input" > /dev/null; then
    jq -n --arg message "$message" '{permission: "deny", agent_message: $message}'
    exit 0
  fi
  echo "$message" >&2
  exit 2
fi
exit 0

このスクリプトはフェイルオープン(fail open)設計だ。jq が見つからない、入力が想定外といった場合は何もブロックせず終了する。コンビニエンス系のフックとして適切な判断だと著者は述べている。

Claude Code(~/.claude/settings.json)とCodex(~/.codex/hooks.json)への登録:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.agents/hooks/require-xcsift.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Cursor(~/.cursor/hooks.json)への登録:

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      {
        "command": "~/.agents/hooks/require-xcsift.sh",
        "matcher": "Shell",
        "timeout": 5
      }
    ]
  }
}

著者が実際に使っている他のフック

  • コンパウンドフック: エージェントがターンを終了したとき、10回以上のターン完了かつ前回実行から2時間以上経過していたら、AGENTS.md を更新するフォローアップメッセージを自動送信する
  • ワークツリークリーンアップ: 古いXcodeワークツリーが蓄積するディスク使用量を検知・報告するフック。セッション開始時やworktreeに言及するシェルコマンドの後に実行される
  • セッションレコーダー: セッション終了時にセッションの記録をファイルに書き出し、後で分析できるようにする

注意点

  • 有効化前に必ずスクリプトを読む: リポジトリやプラグインに同梱されたフックは、自分のマシンで走るスクリプトだ。ダウンロードするスクリプトと同じ扱いをすること
  • フェイルオープンかフェイルクローズドかを意図して選ぶ: 危険なコマンドを守るフックは壊れたときにも deny すべき(Cursorは failClosed、Claude Codeは onFailure で設定)
  • フックは速く保つ: エージェントはフックの完了を待つ。著者はユーザーレベルのフックには全てタイムアウト5秒を設定している
  • エージェントを動かし続けるフックは制限に注意: ターン終了フックがメッセージを送信すると再帰的に動き続ける可能性がある。Cursorは loop_limit(デフォルト5)で制限している
  • フックを唯一の防衛線にしない: ツールによってはフックをスキップするパスが存在する。たとえばCodexでは、PreToolUse フックはコマンドが最初に実行される際には動作するが、すでに承認済みのコマンドをポーリングで再取得する際には再実行されない。フックはあくまで多層的な安全策の一つとして位置づけるべきだ

詳細はClaude Code, Cursor, and Codex Hooks Explainedを参照していただきたい。