Codex

Codex Hooks(フック)とは?設定方法・イベント・使い方の例を解説

INTERCREATE JAPAN 編集部公開 2026.10.15

この記事でわかること

  • フックとは何か、AGENTS.md・スキル・ルールとの違い
  • hooks.jsonとconfig.tomlの書き方、置き場所、最初のフックを動かす手順
  • 12のイベントとmatcher、入力と出力(終了コード・JSON)のしくみ
  • 危険なコマンドの阻止・APIキーの混入防止・テスト結果の確認など、よく使う例
  • Windowsでの書き方(commandWindows)とWSLの注意
  • 信頼の確認・無効化・管理者設定など、安全に使うための注意
目次
  1. Codex Hooksとは
  2. フックでできること
  3. AGENTS.md・スキル・ルールとの違い
  4. Claude Codeのフックとの関係
  5. フックでできないこと・向かないこと
  6. 最初のフックを設定する
  7. 設定ファイルの置き場所
  8. 設定ファイルの形
  9. よく使う設定項目
  10. 手順:最初のフックを動かす
  11. 信頼の確認(/hooks)
  12. イベント・入力・出力のしくみ
  13. イベント:動くタイミング
  14. matcher:どんなときに動かすか
  15. フックが見られるツールの範囲
  16. フックが受け取る入力
  17. フックが返す出力:終了コードとJSON
  18. イベント別:ブロックと続きの作業のさせ方
  19. 追加の情報が大きすぎるとき
  20. よく使う例
  21. 危険なコマンドを止める(PreToolUse)
  22. 編集させないファイルを守る(PreToolUse)
  23. APIキーの貼り付けを防ぐ(UserPromptSubmit)
  24. 終わる前にテストを確認する(Stop)
  25. 作業メモを自動で渡す(SessionStart)
  26. 実行の記録を残す(PostToolUse)
  27. 承認を自動で判断する(PermissionRequest)
  28. フォルダごとに追加の指示を足す
  29. 複数のフックを1つのファイルにまとめる
  30. 使い分けの目安
  31. Windowsでの書き方
  32. Windows専用のコマンドを書く(commandWindows)
  33. Codexのホームフォルダと、WSLの注意
  34. 公式に書かれていないこと
  35. MCPツール・非同期・プラグイン・管理者設定
  36. MCPツールを呼ぶフック
  37. バックグラウンドで動かす(async)
  38. プラグインに同梱する
  39. 管理者が配る(requirements.toml)
  40. 安全に使う・動かないとき
  41. 安全に使うための基本
  42. 手元でスクリプトを試す
  43. フックを無効にする
  44. 動かないときの確認ポイント
  45. よくある質問
  46. まとめ

「テストを実行してから終わって」と毎回頼んでいるのに、たまに忘れられる。「rm -rfだけは絶対に動かしてほしくない」。「チャットにAPIキーを貼り付けてしまわないか心配だ」。Codexを仕事で使い込むほど、こうした悩みが出てきます。

これらを解決するのが、Codexの「フック(Hooks)」です。フックは、Codexの動きの決まったタイミングで、自分で用意したスクリプトを自動的に実行させる機能です。AGENTS.mdのように「お願い」を書くのではなく、条件が合えば毎回必ず動くところが大きな違いです。

この記事では、フックの考え方から、設定ファイルの書き方、イベント・入力・出力のしくみ、よく使う例、Windowsでの書き方、安全に使うための注意までを順に解説します。内容は2026年10月時点の公式ドキュメントをもとにしています。フックは機能の追加が続いているため、細かい仕様は公式ページもあわせて確認してください。

Codex Hooksとは

ノートPCの画面を見ながら、作業の流れの途中に確認の関所を置く設計をメモに書く開発担当者

フックは、Codexの「拡張の仕組み」のひとつです。公式ドキュメントでは、Codexがユーザーの依頼を受けて動いている間(エージェントのループ)に、スクリプトやMCPツールを実行できる仕組みだと説明されています。

「ツールを実行する直前」「応答を終える直前」のように、Codexの動きの途中に、自分の処理を差し込めると考えると分かりやすいでしょう。差し込んだ処理が結果を返すと、Codexはそれを見て、動作を止めたり、続きの作業をしたりします。

フックでできること

公式ドキュメントでは、フックを使うと次のような機能を作れると紹介されています。

  • チャットの内容を、自分たちのログ・分析の仕組みに送る
  • チームのプロンプトを検査して、APIキーを誤って貼り付けていないかを調べ、ブロックする
  • チャットを要約して、記憶として自動的に残す
  • ターンが終わったときに独自の検証を実行し、基準を満たしていなければ続きをやらせる
  • 特定のフォルダで作業するときだけ、指示を追加する

具体的な設定は、のちほど実用例として紹介します。まずは、フックがほかの仕組みとどう違うのかを整理しておきます。

AGENTS.md・スキル・ルールとの違い

Codexには、動きを調整する仕組みがいくつかあります。フックは、「条件が合えば必ず実行される」という点で、ほかと性質が異なります。

仕組み何をするものか確実に実行されるか
AGENTS.mdプロジェクトのルールや前提を、文章でCodexに伝える指示書Codexが読んで従う。守られるかはモデルの判断
スキル作業の手順や専門知識をまとめ、必要なときに読み込ませるCodexが必要と判断したときに使う
ルールサンドボックスの外でCodexが実行してよいコマンドを制御する(公式では実験的な機能)コマンドの実行可否を決める
フックCodexの動きの決まったタイミングで、自分のスクリプトやMCPツールを動かす条件に合えば、そのたびに実行される

たとえば「本番用のファイルは触らないで」とAGENTS.mdに書いても、Codexがそれを読み落とすことはありえます。フックなら、ファイルを書き換える操作そのものを検査して止められます。反対に、「見出しは短くして」のような文章の好みは、フックではなくAGENTS.mdに書く方が向いています。

あわせて読みたいCodex AGENTS.mdとは?書き方・置き場所・読み込まれる順番を解説CodexのAGENTS.mdは、毎回守ってほしいルールを書いておく指示書のファイルです。置き場所と読み込まれる順番、/initでの作り方、書く内容、GitHubのレビュー基準、効かないときの確認方法までを、2026年10月時点の公式情報で解説します。

Claude Codeのフックとの関係

Claude Codeにも「フック」という同じ名前の機能があります。イベントの名前(PreToolUse、Stopなど)や、JSONで結果を返す形は、Claude Codeを使ったことがある方にはなじみのあるものです。公式ドキュメントでも、プラグインのフックには、既存のものと合わせられるようCLAUDE_PLUGIN_ROOTなどの環境変数が用意されると説明されています。

ただし、設定ファイルの置き場所、使えるイベント、「信頼の確認」の流れなどは異なります。Claude Code側の書き方をそのまま持ち込まず、この記事の内容か公式ドキュメントで確かめてください。Claude Codeのフックは、別の記事で詳しく解説しています。

あわせて読みたいClaude Codeのフック(Hooks)とは?設定方法と使い方・例を解説Claude Codeのフック(Hooks)は、決まったタイミングで自動的にコマンドを動かす仕組みです。CLAUDE.mdとの違い、最初の設定手順、イベントと条件の指定、Windowsでの書き方、実用例、安全面の注意まで、2026年10月時点の公式ドキュメントで解説します。

フックでできないこと・向かないこと

フックは便利ですが、公式ドキュメントにも次のような注意があります。

  • ツールのフックは、「便利な安全装置」であって、完全な強制の境界ではありません。一部の特殊なツールの経路は、標準のフックの経路を通らないことがあります
  • ホスト型のツール(Web検索など)は、PreToolUseとPostToolUseの対象外です
  • PostToolUseは、すでに実行されたコマンドの副作用を取り消せません

そのため、絶対に守りたいことは、フックだけに頼らず、サンドボックスや承認の設定とあわせて守る形にします。詳しくは「安全に使う・動かないとき」で説明します。

最初のフックを設定する

PCでhooks.jsonを開いて、動かすスクリプトの場所を書き込んでいる会社員の手元

フックは、hooks.jsonというファイル、またはconfig.tomlの中の[hooks]という項目に書きます。専用のアプリや画面で作るのではなく、設定ファイルに書く形です。

設定ファイルの置き場所

Codexは、有効な設定の層(ユーザーの設定、プロジェクトの設定など)の隣にあるフックを探します。公式ドキュメントでは、実際によく使う場所として次の4つが挙げられています。

場所効く範囲向いている使い方
~/.codex/hooks.json自分のすべての作業個人のログ、通知、プロンプトの検査
~/.codex/config.tomlの[hooks]自分のすべての作業ほかの設定と一緒に管理したいとき
<リポジトリ>/.codex/hooks.jsonそのプロジェクトの作業チームで共有したい検査・テストの実行
<リポジトリ>/.codex/config.tomlの[hooks]そのプロジェクトの作業プロジェクトの設定と一緒に管理したいとき

複数の場所にフックがある場合は、該当するものがすべて読み込まれます。優先度の高い設定が、低い設定のフックを置き換えることはありません。同じ層にhooks.jsonと[hooks]の両方があると、Codexは両方を読み込んで、起動時に警告を出します。1つの層では、どちらか一方に統一するのがおすすめです。

プロジェクト内のフックは、そのプロジェクトの.codex/が「信頼された」状態のときだけ読み込まれます。信頼されていないプロジェクトでも、ユーザーとシステムのフックは、それぞれの層から読み込まれます。

そのほか、有効なプラグインが同梱しているフックも、ほかのフックと一緒に読み込まれます。プラグインについては、のちほど説明します。

設定ファイルの形

フックの設定は、3段の入れ子になっています。

  1. イベントを選ぶどのタイミングで動かすか。PreToolUse(ツールの実行前)やStop(ターンの終了時)などです
  2. matcher(条件)を決めるそのイベントのうち、どんな場合だけ動かすか。「Bashのときだけ」のように絞ります。省略するとすべてに合います
  3. ハンドラー(実行する処理)を書く動かすコマンドなどを指定します。1つの条件に、複数を並べられます

公式ドキュメントの形をもとに、最小限にした例が次のとおりです。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/pre_tool_use_policy.py",
            "statusMessage": "Checking Bash command"
          }
        ]
      }
    ]
  }
}

この例は、PreToolUse(ツールの実行前)というイベントのうち、ツール名がBashのときだけ、commandに書いたコマンドを実行します。statusMessageは、フックの実行中に画面に表示されるメッセージです(省略できます)。

同じ内容を、config.tomlに書くと次のようになります。

[[hooks.PreToolUse]]
matcher = "^Bash$"

[[hooks.PreToolUse.hooks]]
type = "command"
command = 'python3 ~/.codex/hooks/pre_tool_use_policy.py'
statusMessage = "Checking Bash command"

TOMLの書き方では、matcherに^Bash$のような正規表現を書いています。hooks.jsonの例の"Bash"も、同じく正規表現として扱われます(「イベント・入力・出力のしくみ」で説明します)。

よく使う設定項目

ハンドラーに書ける、主な項目は次のとおりです。

項目意味初期値・補足
typeハンドラーの種類command(コマンド)またはmcp_tool(MCPツール)
command実行するコマンドセッションの作業フォルダ(cwd)で実行される
timeout制限時間(秒)省略すると、ほとんどのフックは600秒。SessionEndとInterruptは1秒で、最大3秒
statusMessage実行中に表示するメッセージ省略可
asyncバックグラウンドで実行するかtrueにすると、Codexは終わりを待たずに進む
additionalContextLimitモデルに渡せる追加情報の量の目安(トークン)初期値は2,500。0は制限なし
commandWindowsWindowsだけで使うコマンドTOMLではcommand_windowsも可

公式ドキュメントによると、ハンドラーの種類として使えるのはcommandとmcp_toolです。promptとagentは「解析はされるが、実行はスキップされる」とあり、今のところ動きません。

リポジトリ内のスクリプトを呼ぶときは、.codex/hooks/...のような相対パスより、Gitのルートから解決するパスが勧められています。Codexをサブフォルダから起動することがあるためです。公式の例では、"$(git rev-parse --show-toplevel)/.codex/hooks/..."のような書き方が使われています。

手順:最初のフックを動かす

ここでは、「Bashのコマンドを実行する前に、危険なコマンドを止める」フックを例に、設定から動作確認までを説明します。スクリプトの中身は、「よく使う例」で紹介するものを使います。

  1. スクリプトを置く~/.codex/hooks/の下に、pre_tool_use_policy.pyを保存します。Pythonが入っていない場合は、ほかの言語でも構いません。標準入力でJSONを受け取って、標準出力に返せればよいためです
  2. hooks.jsonを書く上の「設定ファイルの形」の例を、~/.codex/hooks.jsonとして保存します
  3. Codexを起動し直す新しいフックは、起動時に読み込まれます。レビューが必要なフックがあると、起動時に/hooksを開くよう警告が出ます
  4. /hooksで内容を確認して信頼するCLIで/hooksと入力すると、フックのブラウザが開きます。イベントを選んで、中身を確認し、問題がなければ信頼します(次の節で説明します)
  5. 動作を確認するCodexに「rm -rfで一時フォルダを消して」のように頼み、フックが止めることを確かめます。止まらないときは、「安全に使う・動かないとき」の表を確認してください

最初は「何もしない」フックから始めてもよい

いきなり危険な操作を止めるフックを書くと、うまく動かないときにCodexの作業まで止まってしまいます。まずは、動いた記録をファイルに残すだけのフック(asyncで動かすログ)から始めると、読み込まれているか、どんな入力が来ているかを安全に確かめられます。

信頼の確認(/hooks)

フックは、あなたのパソコン上でスクリプトを実行する仕組みです。そのため、管理者が配布した「マネージド」ではないフックは、内容を確認して「信頼」しないと動きません。

  • Codexは、フックの定義の「ハッシュ(内容から計算した識別値)」に対して、信頼したことを記録します
  • 新しく追加されたフック、または定義が変わったフックは、「要レビュー」になり、信頼されるまでスキップされます
  • CLIの/hooksで、フックの置き場所を確認し、新規・変更されたフックを信頼したり、個別のフックを無効にしたりできます
  • システム・MDM・クラウド・requirements.tomlから配られた「マネージド」のフックは、ポリシーで信頼済みとして扱われ、ユーザーは/hooksから無効にできません

信頼の対象は「フックの定義」です。スクリプトの中身だけを書き換えたときに、再確認が必要になるかどうかは、公式ドキュメントには記述がありません。共有のリポジトリでは、スクリプトの変更も、レビューの対象にしておくのが安全です。

信頼の確認をスキップするオプションに注意

公式ドキュメントには、--dangerously-bypass-hook-trustというオプションがあり、「フックの出どころを別の方法で確認済みの、一回限りの自動化」で、信頼の記録なしに有効なフックを動かせると説明されています。名前のとおり危険なので、手元の通常の作業では使わず、使うときも、フックの出どころを自分で確かめられる場面に限りましょう。

イベント・入力・出力のしくみ

ホワイトボードに作業の流れと、フックを差し込む位置を書き込みながら同僚に説明する担当者

ここからは、設定を自分で書いたり、動かないときに原因を探したりするために必要な、フックの内部の考え方を説明します。「いつ動くか(イベント)」「どれを動かすか(matcher)」「何を受け取り、何を返すか(入出力)」の3つを押さえれば、公式ドキュメントも読みやすくなります。

イベント:動くタイミング

2026年10月時点で、公式ドキュメントには12のイベントがあります。動くタイミングで分けると、次の4つです。

いつイベント
ターン(1回のやりとり)の途中PreToolUse、PermissionRequest、PostToolUse、PreCompact、PostCompact、UserPromptSubmit、SubagentStop、Stop
実行中のターンを中断したときInterrupt(サブエージェントでは動かない)
セッションやサブエージェントの開始時SessionStart、SubagentStart
メインのスレッドが終わるときSessionEnd(サブエージェントでは動かない)

それぞれのイベントで何ができるのかを、表にまとめました。「止められるか」は、そのイベントで、動作を止めたり続きの作業をさせたりできるかどうかです。

イベント動くタイミングできること
SessionStartセッションの開始・再開・/clear・圧縮の後・フォークのとき追加の情報(開発者向けの文脈)をモデルに渡す
UserPromptSubmitプロンプトを送る直前プロンプトをブロックする。追加の情報を渡す
PreToolUseツールを実行する直前実行を拒否する。入力を書き換えて実行させる。追加の情報を渡す
PermissionRequestCodexが承認を求める直前承認する・拒否する・判断しない(通常の承認画面に任せる)
PostToolUseツールが出力を返した直後結果を差し替えるフィードバックを返す。追加の情報を渡す。取り消しはできない
PreCompact会話の圧縮(コンパクト)の直前圧縮を止める
PostCompact会話の圧縮の直後圧縮の後で処理を止める
SubagentStartサブエージェントの開始時サブエージェントに追加の情報を渡す(開始は止められない)
SubagentStopサブエージェントの終了時もう一度やり直させる
Stopターンの終了時続きの作業をさせる
Interruptターンを中断したとき記録や後片付け。中断自体は止められない
SessionEndメインのスレッドが終わるとき最終メモの保存や後片付け。助言的な扱いで、Codexの動きは変えられない

SessionEndは、開いたままの会話をアーカイブまたは削除したとき、Codexが通常どおり終了したとき、会話がどの接続中のクライアントでも開かれないまま30分たったときに動きます。ほかの会話に切り替えただけでは、すぐには動きません。

SessionStartは、圧縮の後にも動きます。自動圧縮がターンの途中で起きた場合は、追加の情報が、その続きの最初のモデルへのリクエストに渡されます。圧縮で消えてしまった前提を、もう一度渡す使い方ができます。

あわせて読みたいCodex Skillsとは?作り方・置き場所・使い方を非エンジニア向けに解説Codexのスキル(Skills)は、よく使う手順をまとめて必要なときだけ読み込ませる仕組みです。AGENTS.mdとの違い、作り方、置き場所、呼び出し方、うまく動かないときの対処を、2026年10月時点の公式情報で解説します。

matcher:どんなときに動かすか

matcherは、フックを動かす条件を決める正規表現の文字列です。"*"、空文字、またはmatcher自体を書かないと、そのイベントが起きるたびに動きます。何に対して照合されるかは、イベントごとに決まっています。

イベントmatcherが絞り込む対象値の例
PreToolUse、PostToolUse、PermissionRequestツールの名前Bash、apply_patch、Edit|Write、mcp__filesystem__.*
SessionStart開始の理由startup、resume、clear、compact、fork
PreCompact、PostCompact圧縮のきっかけmanual(手動)、auto(自動)
SessionEnd終わった理由今のところotherのみ
SubagentStart、SubagentStopサブエージェントの種類サブエージェントによって異なる
UserPromptSubmit、Stop、Interrupt使われない書いても無視される

ファイルの編集は、Codexの内部ではapply_patchというツールで行われます。公式ドキュメントでは、このapply_patchに対して、matcherにapply_patchのほか、EditとWriteも使えると説明されています。ただしフックに届く入力のtool_nameは、常にapply_patchです。

MCPツールは、mcp__サーバー名__ツール名という名前で照合します。たとえばmcp__filesystem__read_fileのようにします。

フックが見られるツールの範囲

PreToolUseとPostToolUseは、シェルコマンドだけでなく、ほとんどのローカルの関数ツールで動きます。公式ドキュメントの整理は次のとおりです。

ツールの経路PreToolUsePostToolUsematcherの名前
シェルコマンド○○Bash
統合実行(exec_command)○○Bash
apply_patch(ファイルの編集)○○apply_patch、Edit、Write
MCPツール○○mcp__サーバー名__ツール名
そのほかのローカルの関数ツール○○関数ツールの名前(update_planなど)。spawn_agentはAgentでも照合される
ホスト型ツール(Web検索など)××ローカルの関数ツールの経路を通らない

統合実行で動いているコマンドへ、あとからwrite_stdinで入力や確認をしても、PreToolUseはもう一度動きません。最初のコマンドがPreToolUseを通っているためです。ここは、フックを設計するときの落とし穴になりやすい点です。

フックが受け取る入力

コマンドのフックは、標準入力で、1つのJSONオブジェクトを受け取ります。どのイベントでも共通して入っている項目は、次のとおりです。

項目型意味
session_id文字列現在のセッションのID。サブエージェントのフックでは、親のセッションのID
transcript_path文字列またはnull会話の記録ファイルの場所(ある場合)
cwd文字列セッションの作業フォルダ
hook_event_name文字列いま動いているイベントの名前
model文字列使用中のモデルの名前(Codex独自の項目)

ターンの中で動くイベントには、turn_id(ターンのID)も入っています。SessionStart、PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit、SubagentStart、SubagentStop、Stop、Interruptには、現在の権限モードを示すpermission_mode(default、acceptEdits、plan、dontAsk、bypassPermissionsのいずれか)も入ります。

そのうえで、イベントごとに次のような項目が加わります。

イベント加わる主な項目
SessionStartsource(startup・resume・clear・compact・fork)
UserPromptSubmitprompt(送ろうとしているプロンプト)
PreToolUsetool_name、tool_use_id、tool_input(Bashとapply_patchではtool_input.command)
PermissionRequesttool_name、tool_input(tool_input.descriptionに承認の理由が入ることがある)
PostToolUsetool_name、tool_use_id、tool_input、tool_response(ツールの出力)
Stopstop_hook_active(すでにStopで続きの作業をさせたターンか)、last_assistant_message(最後の応答の文章)
SubagentStart・SubagentStopagent_id、agent_type。Stopにはagent_transcript_path、last_assistant_messageなども
PreCompact・PostCompacttrigger(manualかauto)
SessionEndreason(今のところother)

公式ドキュメントでは、transcript_pathが指す会話の記録ファイルの形式は「安定した仕様ではなく、変わる可能性がある」と書かれています。記録ファイルの中身を読む処理は、壊れやすいので、避けるのが無難です。

たとえば、Bashでrm -rf buildを実行しようとしたときのPreToolUseには、次のようなJSONが届きます(項目は一部を省略しています)。

{
  "session_id": "thr_123",
  "cwd": "/workspace",
  "hook_event_name": "PreToolUse",
  "permission_mode": "default",
  "tool_name": "Bash",
  "tool_use_id": "call_001",
  "tool_input": { "command": "rm -rf build" }
}

この入力に対して、フックが次のJSONを標準出力に返すと、Codexはコマンドを実行せず、理由をCodexに伝えます。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "rm -rf は使えません。別の方法を考えてください。"
  }
}

フックが返す出力:終了コードとJSON

フックの結果は、終了コードと、標準出力のJSONで返します。基本のルールは次のとおりです。

  • 終了コード0で何も出力しない:成功として扱われ、Codexはそのまま続けます
  • 終了コード0でJSONを出力する:JSONの内容に従って、ブロックや追加の情報の受け渡しをします
  • 終了コード2で、標準エラーに理由を書く:ブロックを意味します。PreToolUse・PostToolUse・UserPromptSubmit・SubagentStop・Stopで使えると、公式ドキュメントに書かれています

イベントによっては、標準出力の「ふつうの文章」の扱いも異なります。

イベント標準出力の文章
SessionStart、UserPromptSubmit追加の開発者向けの文脈として、モデルに渡される
SubagentStartサブエージェントへの追加の文脈として渡される
PreToolUse、PermissionRequest、PostToolUse、PreCompact、PostCompact無視される
SubagentStop、Stop、Interrupt文章だけの出力は無効(JSONを出す。何も出さないのは成功)

0と2以外の終了コードが、イベントごとにどう扱われるかは、公式ドキュメントに詳しい記述がありません。ブロックしたいときは、終了コード2か、JSONの判断を使いましょう。

イベント別:ブロックと続きの作業のさせ方

JSONで返す形は、イベントごとに少しずつ違います。実際に使う場面が多い順に整理します。

やりたいこと返すJSON(要点)
ツールの実行を拒否する(PreToolUse)hookSpecificOutputの中に、"permissionDecision": "deny"と"permissionDecisionReason"を入れる。古い形の{"decision": "block", "reason": "..."}も使える
ツールの入力を書き換えて実行させる(PreToolUse)"permissionDecision": "allow"と"updatedInput"を入れる。Bashとapply_patchでは、updatedInputに文字列のcommandが必要
承認を自動で許可・拒否する(PermissionRequest)hookSpecificOutputの中に、"decision": {"behavior": "allow"}または{"behavior": "deny", "message": "..."}を入れる
プロンプトをブロックする(UserPromptSubmit){"decision": "block", "reason": "..."}。または終了コード2
続きの作業をさせる(Stop){"decision": "block", "reason": "..."}。reasonが、新しいユーザーのプロンプトとして扱われる
ツールの結果を差し替える(PostToolUse){"decision": "block", "reason": "..."}。ツールの結果が、このフィードバックに置き換わる
追加の情報を渡す(複数のイベント)hookSpecificOutputの中に、"additionalContext"を入れる
警告を表示する(多くのイベント)"systemMessage"に文章を入れる

いくつか、見落としやすい決まりがあります。

  • PreToolUseでpermissionDecision: "ask"、continue: false、stopReason、suppressOutputを返しても、まだ対応していません。フックの実行が「失敗」として報告され、ツールの呼び出しはそのまま続きます
  • PreToolUseのupdatedInputは、permissionDecision: "allow"と一緒にだけ返せます
  • PermissionRequestは、複数のフックが判断を返した場合、1つでもdenyがあれば拒否が優先されます。拒否がなく、許可があれば、承認の画面を出さずに進みます。どれも判断しなければ、通常の承認の流れになります
  • PermissionRequestは、そもそも承認が必要ないコマンドでは動きません
  • Stopのblockは「ターンを却下する」のではなく、Codexに続きをさせるための新しいプロンプトを作ります。Stopのフックが複数あり、1つでもcontinue: falseを返すと、それが優先されます

Stopのフックは、無限に続きをさせない工夫が必要

Stopで続きの作業をさせると、その作業が終わったときに、またStopのフックが動きます。毎回ブロックを返す作りにすると、終わりません。入力に入っているstop_hook_activeがtrueのときは、何もせずに終了コード0で終える、という書き方が基本です(「よく使う例」で紹介します)。

追加の情報が大きすぎるとき

フックが返すadditionalContextは、モデルに渡る情報です。公式ドキュメントでは、モデルに見える、1回のフックの出力は、初期設定でおよそ2,500トークンまでと説明されています。それを超えると、全文は一時フォルダのhook_outputs/<セッションID>/の下にファイルとして保存され、モデルには「先頭と末尾の抜粋」と、保存先のパスが渡されます。

上限は、ハンドラーごとのadditionalContextLimitで変えられます。ただし、複数のフックやプラグインの情報は積み重なるため、上限を上げすぎると、モデルの性能が落ちる原因になります。0(制限なし)は、出力の量を自分で厳しく抑えているフック以外では使わないよう、注意書きがあります。

もうひとつ、重要な注意があります。大きな出力はディスクに保存されることがあるため、フックの出力に、秘密情報やパスワードなどを含めないようにしてください。

よく使う例

PCの画面に並んだ複数のスクリプトの例を見比べて、自分の環境に合うものを選ぶ会社員

ここでは、実際に使いやすい例を紹介します。スクリプトは、この記事のために作った例で、公式ドキュメントに載っているものではありません。入力と出力の形は公式ドキュメントに沿わせ、手元のPythonで、標準入力にJSONを渡して出力を確かめています。Codexに組み込んだ動作は、ご自身の環境で確かめてから使ってください。

どの例も、Pythonで書いています。標準入力のJSONを読んで、標準出力に結果を返す形ならよいので、ほかの言語でも同じように書けます。

危険なコマンドを止める(PreToolUse)

もっとも定番の使い方です。Bashのコマンドを実行する直前に中身を検査し、「使わせたくない」ものを拒否します。

hooks.jsonは、先ほどの「設定ファイルの形」の例と同じです。スクリプトの例は次のとおりです。

import json
import sys

event = json.load(sys.stdin)
command = event.get("tool_input", {}).get("command", "")

BLOCKED = ["rm -rf", "git push --force", "git reset --hard"]

for word in BLOCKED:
    if word in command:
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": f"{word} は使えません。別の方法を考えてください。",
            }
        }, ensure_ascii=False))
        sys.exit(0)

# 何も出力せずに終了コード0なら、そのまま実行される

拒否のときは、permissionDecisionReasonに理由を書いておきます。この理由はCodexに伝わるので、Codexは別の方法を考え直します。

文字列の検査は、すり抜けやすい

この例は、コマンドの中に決まった文字列があるかを調べるだけです。rm -frやgit push -fのように、書き方を変えると通ってしまいます。確実に守りたいときは、検査する文字列を増やすだけでなく、サンドボックスや承認の設定、バージョン管理(Gitで戻せる状態)と組み合わせてください。

編集させないファイルを守る(PreToolUse)

ファイルの編集はapply_patchというツールで行われ、公式ドキュメントでは、tool_input.commandに入ると説明されています。この中身を検査すれば、特定のファイルへの変更を拒否できます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "apply_patch",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/protect_files.py",
            "statusMessage": "Checking protected files"
          }
        ]
      }
    ]
  }
}
import json
import sys

event = json.load(sys.stdin)
patch = str(event.get("tool_input", {}).get("command", ""))

PROTECTED = [".env", "secrets/", "package-lock.json"]

for name in PROTECTED:
    if name in patch:
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": f"{name} は編集しないルールです。別のファイルで対応してください。",
            }
        }, ensure_ascii=False))
        sys.exit(0)

パッチの書式の細かい中身は、公式のフックのページには書かれていません。この例は、「パッチの文章にそのファイル名が含まれるか」だけを見る簡単なものです。.envを指定すると、.env.exampleも対象になってしまうなど、ファイル名が部分的に合う場合も止まります。実際に使うときは、動かしてみて、止まり方を確かめてください。

APIキーの貼り付けを防ぐ(UserPromptSubmit)

プロンプトを送る直前に中身を調べ、APIキーのような文字列が入っていたらブロックします。公式ドキュメントが最初に挙げている使い方のひとつです。

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/user_prompt_check.py"
          }
        ]
      }
    ]
  }
}
import json
import re
import sys

event = json.load(sys.stdin)
prompt = event.get("prompt", "")

if re.search(r"sk-[A-Za-z0-9_-]{20,}", prompt):
    print("APIキーのような文字列が含まれています。削除してから送り直してください。", file=sys.stderr)
    sys.exit(2)

終了コード2で、標準エラーに理由を書くと、プロンプトがブロックされます。UserPromptSubmitでは、matcherは使われないので、書く必要はありません。

この例のsk-で始まるパターンは、一例です。サービスごとにキーの形式は異なるため、自社で使っているサービスに合わせて、パターンを書き換えてください。

終わる前にテストを確認する(Stop)

Codexが「終わりました」と応答しようとしたときに、テストを実行し、失敗していたら続きの作業をさせます。「テストを通してから終わって」と毎回頼む必要がなくなります。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/stop_check.py",
            "timeout": 120,
            "statusMessage": "Running tests"
          }
        ]
      }
    ]
  }
}
import json
import subprocess
import sys

event = json.load(sys.stdin)

# すでにこのフックで続きの作業をさせた後なら、ここで終わらせる(無限ループの防止)
if event.get("stop_hook_active"):
    sys.exit(0)

result = subprocess.run(
    ["npm", "test", "--silent"],  # 自分のプロジェクトのテストのコマンドに置き換える
    capture_output=True,
    text=True,
)

if result.returncode != 0:
    print(json.dumps({
        "decision": "block",
        "reason": "テストが失敗しています。失敗した理由を確認して直してください。",
    }, ensure_ascii=False))

ポイントは3つあります。

  • stop_hook_activeがtrueのときは何もせずに終えて、続きの作業が終わらなくなることを防ぎます
  • テストが通ったときは、何も出力せず、終了コード0で終わります。Stopでは、文章だけの出力は無効なので、出力するならJSONにします
  • reasonに書いた文章は、新しいユーザーのプロンプトのようにCodexへ渡ります。「何を直してほしいか」が分かるように書きます

テストに時間がかかるプロジェクトでは、timeoutを長めにしつつ、毎回のターンの終わりに待たされる点に注意が必要です。変更したファイルに関係するテストだけを動かす、といった工夫も考えましょう。

作業メモを自動で渡す(SessionStart)

セッションの開始時に、前回の作業メモを読み込んでCodexに渡します。SessionStartでは、標準出力の文章が、そのまま追加の文脈として渡されます。

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume|compact",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/session_start.py",
            "statusMessage": "Loading session notes"
          }
        ]
      }
    ]
  }
}
from pathlib import Path

note = Path(".codex/NOTES.md")
if note.exists():
    print("前回までの作業メモ:")
    print(note.read_text(encoding="utf-8")[:2000])

matcherにcompactを含めておくと、会話が圧縮されたあとにも、メモを渡し直せます。渡す量が多すぎると、先ほど説明したとおり抜粋に置き換わるので、メモは短くまとめておきましょう。

この仕組みは、AGENTS.mdで足りないときの補助です。いつも守ってほしい決まりごとは、AGENTS.mdに書く方が管理しやすくなります。

実行の記録を残す(PostToolUse)

ツールが実行されたあとに、何を実行したかをログのファイルに追記します。asyncをtrueにして、Codexの動きを遅くしないようにします。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/post_tool_log.py",
            "async": true,
            "timeout": 30
          }
        ]
      }
    ]
  }
}
import json
import sys
from datetime import datetime
from pathlib import Path

event = json.load(sys.stdin)
line = {
    "time": datetime.now().isoformat(timespec="seconds"),
    "tool": event.get("tool_name"),
    "command": str(event.get("tool_input", {}).get("command", ""))[:200],
}
log = Path.home() / ".codex" / "hook-log.jsonl"
log.parent.mkdir(parents=True, exist_ok=True)
with log.open("a", encoding="utf-8") as f:
    f.write(json.dumps(line, ensure_ascii=False) + "\n")

バックグラウンドのフックは、実行をブロックしたり、書き換えたりはできません。記録や通知のように、「やってもやらなくても作業の流れが変わらない」処理に向いています。

承認を自動で判断する(PermissionRequest)

Codexが承認を求めようとしたとき、フックで許可か拒否を決められます。毎回、確認の画面に答えるのが面倒な、安全なコマンドを自動で許可する使い方です。

import json
import sys

event = json.load(sys.stdin)
command = str(event.get("tool_input", {}).get("command", ""))

if command.startswith(("git status", "git diff", "git log")):
    decision = {"behavior": "allow"}
elif command.startswith("git push"):
    decision = {"behavior": "deny", "message": "pushは人が手元で行います。"}
else:
    sys.exit(0)  # 判断しない。通常の承認画面に任せる

print(json.dumps({
    "hookSpecificOutput": {
        "hookEventName": "PermissionRequest",
        "decision": decision,
    }
}, ensure_ascii=False))

どれにも当てはまらないときは、何も出力せずに終えます。その場合は、通常の承認の流れ(画面で人が確認する)になります。許可を出す条件は、できるだけ狭く書きましょう。gitで始まるコマンドを全部許可する、といった書き方は危険です。

フォルダごとに追加の指示を足す

公式ドキュメントでは、フックの使い方として「特定のフォルダにいるときのプロンプトを調整する」ことも紹介されています。たとえば、UserPromptSubmitのフックで、入力のcwdを見て、フォルダに応じた注意書きを標準出力に出します。

import json
import sys

event = json.load(sys.stdin)
cwd = event.get("cwd", "")

if cwd.replace("\\", "/").rstrip("/").endswith("/docs"):
    print("このフォルダは公開前の文書です。数字と固有名詞は、出典を確認してから書いてください。")

UserPromptSubmitでは、標準出力の文章が、追加の開発者向けの文脈として渡されます。フォルダごとの指示を増やしたい場合は、AGENTS.mdをフォルダごとに置く方法も使えます。ふだんの指示は、AGENTS.mdのほうが管理しやすいでしょう。

複数のフックを1つのファイルにまとめる

ここまでの例を、1つの~/.codex/hooks.jsonにまとめると、次のようになります。同じイベントに複数のグループを並べられるため、「Bashを検査するフック」と「ファイルの編集を検査するフック」を分けて書けます。

{
  "description": "個人用の安全装置とログ",
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          { "type": "command", "command": "python3 ~/.codex/hooks/user_prompt_check.py" }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 ~/.codex/hooks/pre_tool_use_policy.py" }
        ]
      },
      {
        "matcher": "apply_patch",
        "hooks": [
          { "type": "command", "command": "python3 ~/.codex/hooks/protect_files.py" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 ~/.codex/hooks/post_tool_log.py", "async": true, "timeout": 30 }
        ]
      }
    ]
  }
}

先頭のdescriptionは、ファイルの説明を書くための任意の項目で、どのフックが動くかには影響しません。まとめたあとも、フックごとに/hooksで信頼の確認が必要です。1つずつ追加して、そのたびに動作を確かめると、どれが原因かを切り分けやすくなります。

使い分けの目安

ここまでの例を、「何をしたいか」から引けるようにまとめます。

やりたいこと使うイベントブロックできるか
危険なコマンドを止めるPreToolUse(matcher: Bash)できる
特定のファイルの編集を止めるPreToolUse(matcher: apply_patch)できる
APIキーの貼り付けを防ぐUserPromptSubmitできる
終わる前にテストを確認するStop続きの作業をさせられる
開始時に前提を渡すSessionStart止めるものではない
実行の記録を残すPostToolUse(async)できない(取り消しはできない)
承認を自動で決めるPermissionRequest許可・拒否ができる
圧縮の後に前提を渡し直すSessionStart(matcher: compact)止めるものではない

Windowsでの書き方

Windowsのノートパソコンで、PowerShellの画面を見ながらスクリプトの場所を確認する会社員

公式ドキュメントのフックの例は、python3や/usr/bin/python3、$(git rev-parse --show-toplevel)など、macOSやLinuxの書き方が中心です。Windowsで使うときは、いくつか読み替えが必要です。ここでは、公式に書かれていることと、書かれていないことを分けて説明します。

Windows専用のコマンドを書く(commandWindows)

フックのハンドラーには、Windowsだけで使うコマンドを別に書けるcommandWindowsという項目があります。TOMLではcommand_windowsという書き方もできます。hooks.jsonのなかに、macOS・Linux用のcommandと、Windows用のcommandWindowsを並べておけば、同じ設定ファイルを共有できます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/pre_tool_use_policy.py",
            "commandWindows": "py -3 C:\\Users\\yamada\\.codex\\hooks\\pre_tool_use_policy.py",
            "statusMessage": "Checking Bash command"
          }
        ]
      }
    ]
  }
}

ポイントは次の3つです。

  • commandWindowsのpy -3は、Pythonを入れると付いてくる「Pythonランチャー」の呼び出しです。公式の管理者向けの例(command_windows)でも、py -3 C:\enterprise\hooks\...という形が使われています
  • JSONの中では、パスの区切りの\を\\と2つ重ねて書きます
  • TOMLでは、シングルクォートで囲んだ文字列(リテラル文字列)なら、\をそのまま書けます(公式の例もそうなっています)
[[hooks.PreToolUse.hooks]]
type = "command"
command = 'python3 ~/.codex/hooks/pre_tool_use_policy.py'
command_windows = 'py -3 C:\Users\yamada\.codex\hooks\pre_tool_use_policy.py'

Windowsには、python3という名前のコマンドが標準では入っていない場合があります。python3のまま使うと「コマンドが見つからない」となるので、commandWindowsでpy -3を使うか、Pythonの実行ファイルのフルパスを書くのが確実です。

Codexのホームフォルダと、WSLの注意

公式ドキュメントによると、WindowsのCodex(ChatGPTデスクトップアプリと、Windows上のネイティブなCodex)が使うホームフォルダは、%USERPROFILE%\.codexです。つまり、「ユーザーの設定として置くフック」は、%USERPROFILE%\.codex\hooks.jsonに置くことになります。

注意したいのが、WSL(Windows上のLinux環境)です。公式ドキュメントでは、WSLの中でCodex CLIを動かすと、既定ではLinux側のホームフォルダ(~/.codex)を使うため、Windows側の設定や認証、セッションの履歴は共有されないと説明されています。フックの設定も同じです。Windows側のhooks.jsonに書いても、WSLのCodexには届きません。

Codexを動かしている場所ホームフォルダフックの置き場所(ユーザー用)
Windows(PowerShell)%USERPROFILE%\.codex%USERPROFILE%\.codex\hooks.json
WSLの中Linux側の~/.codexLinux側の~/.codex/hooks.json
WSLでWindowsのホームを使いたいCODEX_HOMEで指定する(例:/mnt/c/Users/<ユーザー名>/.codex)指定したフォルダのhooks.json

プロジェクトのなかの.codex/hooks.jsonは、そのリポジトリにあるため、WindowsとWSLのどちらから開いても読み込まれます(プロジェクトを信頼している場合)。チームで共有するフックは、こちらに置く方が迷いません。

公式に書かれていないこと

Windowsで動かすうえで、次の点は、2026年10月時点で公式ドキュメントのフックのページに記述が見当たりませんでした。うのみにせず、自分の環境で試してから使ってください。

確認したいこと公式ドキュメントの記述対処の目安
コマンドがどのシェル(PowerShell・コマンドプロンプトなど)で実行されるか記述なしパイプやクォートを多用しない。1つのスクリプトを呼ぶだけの形にする
$(git rev-parse --show-toplevel)のような書き方が使えるか公式の例はあるが、Windowsでの動作の記述はなしWindows用は絶対パスで書くか、スクリプトの中でGitのルートを調べる
標準入力のJSONの文字コード記述なし日本語を含むプロンプトで試す。Pythonなら、標準入出力をUTF-8に設定してから読む
アプリやIDE拡張での/hooksに当たる画面CLIの/hooksのみ記述ありCLIで/hooksを開いて確認する

日本語のWindowsでは、Pythonの標準入出力が、既定でUTF-8ではなくcp932になることがあります。日本語のメッセージを返すスクリプトでは、次の2行を最初に入れておくと、文字化けを避けやすくなります。

import sys

sys.stdin.reconfigure(encoding="utf-8")
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")

Codex側がJSONをどの文字コードで渡しているかは公式に書かれていないため、この2行が必ず必要・正しいと言い切れるわけではありません。日本語を含む入力で、実際に試して確かめてください。

まず「動いているか」を目で確かめる

Windowsでは、パスや文字コードで、最初はつまずきやすくなります。最初のフックは、statusMessageを付けて、フックが動いている表示を確認し、標準エラーに短い文章を出すだけのスクリプトから始めると、原因を切り分けやすくなります。

あわせて読みたいCodexをWindowsで使う方法|アプリ・CLIの入れ方とWSLが要るかを解説CodexをWindowsで使う方法を、2026年10月時点の公式情報でまとめました。デスクトップアプリとCLIの入れ方、WSLが必要かどうか、Windowsのサンドボックス、つまずきやすい点の対処、会社のPCで使うときの確認事項まで解説します。

MCPツール・非同期・プラグイン・管理者設定

複数の部品を並べて、用途ごとの組み合わせをホワイトボードで整理しているエンジニアと事務担当

ここまでは、手元のスクリプトを動かす「コマンドのフック」を中心に説明してきました。このほかにも、フックには、MCPツールを呼ぶ形、バックグラウンドで動かす形、プラグインに同梱する形、管理者が配る形があります。

MCPツールを呼ぶフック

MCPツールのフックは、すでに接続しているMCPサーバーのツールを、イベントに合わせて呼ぶ形です。スクリプトを書かずに、既存のスキャナーや検査用のサーバーを使いたいときに向いています。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "scanner",
            "tool": "scan_patch",
            "input": { "patch": "${tool_input.command}" },
            "timeout": 30,
            "statusMessage": "Scanning edited files"
          }
        ]
      }
    ]
  }
}

これは公式ドキュメントの例をもとにしたもので、ファイルを編集した後に、scannerというMCPサーバーのscan_patchツールを呼びます。設定する項目は次のとおりです。

項目意味
type必ずmcp_tool
server接続済みのMCPサーバーの名前(必須)
toolそのサーバーが持つツールの名前(必須)
inputツールに渡す引数のJSON。省略すると{}
timeout実行の制限時間(秒)。省略すると600秒
statusMessage実行中に表示するメッセージ

inputの中では、${tool_input.file_path}のように、${項目名}でフックのイベントの入力を取り出して、引数に組み込めます。値全体が置き換わる場合は、数値などの型がそのまま保たれます。

注意点もあります。

  • フックは、既存のMCP接続を使います。サーバーを新しく起動したり、再接続したりはしません
  • ツールがブロックの判断を返せば、操作を止められます。ただし、エラー・サーバーが見つからない・ツールが使えない、といった場合は、操作を止めません
  • MCPツールのフックは同期的に動き、ツールの承認や、ほかのフックの呼び出しは行いません
  • SessionStartは、MCPサーバーの準備ができる前に動くことがあります。その場合、セッションの開始はブロックされません
  • SessionEndでは、MCPツールのフックは使えません
あわせて読みたいCodex MCPとは?つなぎ方・codex mcp addの使い方・安全な設定Codex MCPは、Codexを外部のツールやドキュメントにつなぐ仕組みです。使える画面、つなぎ方、config.tomlの設定項目、認証、承認と管理者の制限、動かないときの対処までを、2026年10月時点の公式情報でまとめました。

バックグラウンドで動かす(async)

コマンドのフックは、初期設定ではCodexが終わりを待ってから、次に進みます。"async": trueを付けると、フックをバックグラウンドで動かし、Codexは待たずに進みます。

  • 入力、matcher、信頼の確認、timeout、大きな出力の扱いは、同期のフックと同じです
  • 終わったフックの出力は、次の安全なタイミングでCodexに届きます。ターンの途中なら、現在のモデルへのリクエストとツール呼び出しが終わった後、次のリクエストに加わります。ターンが動いていなければ、次のユーザーのターンまで待ちます
  • バックグラウンドのフックは、ブロック・承認・書き換えなど、操作を制御することはできません。ツールの方針、承認の判断、プロンプトの拒否、ターンの続行には、同期のフックを使います
  • 1つのセッションで同時に動かせるバックグラウンドのフックは、最大8つです。それ以上は、待たされます
  • セッションが終わると、終わっていないバックグラウンドのフックは取り消され、まだ届いていない出力は破棄されます
  • SessionEndは、asyncを指定しても、常に同期で動きます

プラグインに同梱する

プラグインは、スキルやMCPサーバーなどをひとまとめにして配る仕組みで、フックも同梱できます。有効にしたプラグインのフックは、ユーザー・プロジェクト・管理者のフックと一緒に読み込まれます。

  • プラグインのルートのhooks/hooks.jsonが、既定で読み込まれます。.codex-plugin/plugin.jsonのhooksで、別のパスやインラインの定義に変えることもできます
  • プラグインのフックには、PLUGIN_ROOT(インストールされたプラグインのルート)とPLUGIN_DATA(書き込める専用フォルダ)という環境変数が渡されます。既存のプラグインと合わせるためのCLAUDE_PLUGIN_ROOTとCLAUDE_PLUGIN_DATAも設定されます
  • プラグインを入れたり有効にしたりしても、フックが自動で信頼されるわけではありません。内容を確認して信頼するまで、スキップされます
  • Webでプラグインを入れても、フックのスクリプト自体は配られません。スクリプトは、実行する環境に用意されている必要があります。企業では、MDM(端末の管理ツール)で配る方法があります

他社のプラグインを入れるときは、フックの定義と、呼んでいるスクリプトの中身を確認してから信頼しましょう。

管理者が配る(requirements.toml)

企業や団体では、管理者がrequirements.tomlに[hooks]を書いて、フックを強制できます。公式ドキュメントで説明されている主な設定は、次のとおりです。

設定意味
[hooks]管理者が強制するフック。イベントごとの書き方は、config.tomlの[hooks]と同じ
managed_dirmacOSとLinuxで、管理されたフックのスクリプトを置くフォルダ(絶対パス)
windows_managed_dirWindowsでの、同じフォルダ(絶対パス)
[features].hooks = trueユーザーがフックを無効にしていても、管理者のフックを有効にしておく
allow_managed_hooks_only = trueユーザー・プロジェクト・セッション・プラグインのフックを無視して、管理者のフックだけを動かす

公式ドキュメントでは、Codexはスクリプト自体は配らないと説明されています。管理者が、MDMなどの仕組みで、スクリプトを端末に入れる必要があります。管理者のフックのコマンドには、managed_dirの下の絶対パスを使うことが勧められています。

ChatGPTのクラウドで動くWorkなど「クラウドが全体を制御する」使い方では、使えるフックが異なります。管理者が定義したリモートのMCPフックのみが対象で、コマンドのフック、ローカルの設定のフック、プラグインのフックは対象外です。MCPフックは、完全な監査の記録にはならないとも明記されているため、導入前に、動作を確かめるよう注意書きがあります。

安全に使う・動かないとき

盾と鍵のアイコンの横で、フックの一覧と信頼の確認画面をチェックする会社員

フックは、手元のパソコンでスクリプトを実行する仕組みです。便利な分、書き方を誤ると、Codexの作業が止まったり、意図しないものを実行したりします。使い始める前に、次の点を確認しておきましょう。

安全に使うための基本

  • 中身を確認してから信頼する:/hooksに出てくるフックは、定義と、呼んでいるスクリプトの両方を読みます。他人から受け取った設定や、プラグインのフックは特に注意します
  • フックだけに頼らない:公式ドキュメントでも、ツールのフックは「便利な安全装置」で、完全な強制の境界ではないとされています。サンドボックスや承認の設定、Gitで元に戻せる状態と組み合わせます
  • 出力に秘密情報を含めない:大きな出力は、一時フォルダのファイルに保存されます。パスワードやAPIキーを標準出力に出さないようにします
  • 複数のフックの順番に頼らない:同じイベントに合うコマンドのフックは、並行して起動されます。「Aのあと、Bが動く」ことを前提にした作りにはしません
  • 許可を出すフックは、条件を狭く:PermissionRequestで許可を返すフックは、承認画面を出さずに進めてしまいます。確かなときだけ許可し、迷うときは判断しない(何も出力しない)ようにします
  • 処理は短く、軽く:同期のフックは、終わるまでCodexが待ちます。時間がかかるものは、asyncにするか、timeoutを短くします

書き換え(updatedInput)は慎重に

PreToolUseでは、ツールの入力を書き換えて実行させることもできます。便利な反面、Codexが出したコマンドと、実際に動くコマンドが別のものになります。書き換えるときは、何をどう変えたかをログに残すなど、あとから追える形にしておきましょう。

手元でスクリプトを試す

フックのスクリプトは、Codexに組み込む前に、単体で動かして出力を確かめられます。入力のJSONをファイルに保存して、標準入力として渡す方法です。

{"tool_name": "Bash", "tool_input": {"command": "rm -rf build"}}

このJSONをinput.jsonとして保存し、次のように実行します。

# macOS・Linux・WSL
python3 pre_tool_use_policy.py < input.json

# Windows(コマンドプロンプト経由)
cmd /c "py -3 pre_tool_use_policy.py < input.json"

PowerShellのパイプ(|)で文字列を渡す方法は、環境によっては、先頭にBOM(目に見えない印)が付いて、JSONとして読めなくなることがあります。手元の確認でも、PowerShell 5.1からパイプで渡したところ、Unexpected UTF-8 BOMというエラーになりました。ファイルに保存して渡す方法のほうが確実です。

確かめたいのは、次の2点です。

  • 拒否したいときに、期待どおりのJSONが標準出力に出て、終了コードが0になっているか(または終了コード2で、標準エラーに理由が出ているか)
  • 何もしないときに、何も出力せず、終了コード0で終わるか

フックを無効にする

フックは、初期設定で有効です。止めたいときは、いくつかの段階があります。

やりたいこと方法
特定のフックだけを止めるCLIの/hooksで、そのフックを無効にする(管理者のフックは無効にできない)
フックの機能をすべて止めるconfig.tomlに[features]のhooks = falseを書く。codex_hooksは古い別名として残っている
プロジェクトのフックだけを読み込ませないそのプロジェクトを「信頼しない」状態にする。信頼されていないプロジェクトの.codex/の層(設定・フック・ルール)は読み込まれない
管理者のフック以外を止めるrequirements.tomlでallow_managed_hooks_only = true(管理者の設定)
[features]
hooks = false

管理者は、requirements.tomlの[features].hooks = trueで、ユーザーが無効にしていても、管理者のフックを有効にしておけます。

動かないときの確認ポイント

「フックが動かない」「止めたいものが止まらない」ときは、次の表で原因を探してください。

症状確認すること対処
フックがまったく動かない/hooksで「要レビュー」になっていないか内容を確認して信頼する。新規・変更したフックは、信頼されるまでスキップされる
プロジェクトのフックだけ動かないそのプロジェクトの.codex/が信頼されているかプロジェクトを信頼する。信頼していないと、プロジェクト内のフックは読み込まれない
設定を直したのに動かない定義が変わって、再び「要レビュー」になっていないか/hooksで再度信頼する
起動時に警告が出る同じ層にhooks.jsonと[hooks]が両方ないかどちらか一方に統一する
フック自体が止まっている[features]のhooks = falseになっていないか該当の設定を消すかtrueにする
特定のツールにだけ効かないmatcherの名前が合っているか。apply_patch、Edit、Write、mcp__...の書き方matcherは正規表現。^Bash$のように、完全一致にすることもできる
Web検索に効かないホスト型のツールは、フックの対象外別の方法(設定・承認)で制御する
拒否したのにコマンドが動いた未対応の項目(permissionDecision: "ask"、continue: falseなど)を返していないか返し方を、拒否の形(deny)か終了コード2に直す。未対応の項目は「失敗」扱いで、ツールの呼び出しは続く
Stopのフックが終わらないstop_hook_activeを見ているかtrueのときは、ブロックを返さずに終える
追加の情報が、途中で切れる約2,500トークンの上限を超えていないか出力を短くする。additionalContextLimitは上げすぎない
Windowsで「コマンドが見つからない」python3を使っていないかcommandWindowsにpy -3を書く
日本語が文字化けする標準入出力の文字コードスクリプトの先頭で、UTF-8を指定して試す
WSLで動かないWSLのCodexは、Linux側のホームを見ていないかLinux側の~/.codexに置くか、CODEX_HOMEを指定する

それでも原因が分からないときは、フックの先頭で、受け取った入力を、ファイルに書き出して確かめるのが近道です。どのフィールドが、どんな値で届いているかが分かります。ただし、入力には、プロンプトやコマンドの内容が含まれます。調査が終わったら、記録の設定は外しましょう。

また、OpenTelemetry(動作の記録を外部に送る仕組み)を設定している場合は、公式ドキュメントの指標に、フックの実行回数hooks.runと実行時間hooks.run.duration_msがあります。フック名、設定の出どころ、成否ごとに集計できるため、組織で運用するときの確認に役立ちます。

あわせて読みたいClaude CodeとCodexを併用する方法|設定の共通化と役割分担の例Claude CodeとCodexを両方使うときのやり方を整理しました。契約と費用、指示ファイル(AGENTS.md・CLAUDE.md)の共通化、書く側とレビューする側の役割分担、Claude Codeから呼べる公式プラグイン、設定の取り込み、並行作業の注意点を2026年10月時点の公式情報でまとめています。

よくある質問

Codexのフックは、初期設定で使えますか?

公式ドキュメントでは、フックは初期設定で有効と説明されています。ただし、フックそのものは自分で作る必要があります。また、管理者が配ったもの以外のフックは、内容を確認して「信頼」するまで動きません。

フックの設定ファイルは、どこに置けばよいですか?

自分用なら~/.codex/hooks.json(Windowsでは%USERPROFILE%\.codex\hooks.json)、プロジェクトで共有するなら<リポジトリ>/.codex/hooks.jsonが基本です。config.tomlの[hooks]に書くこともできます。

hooks.jsonとconfig.tomlのどちらに書くのがよいですか?

機能に違いはありません。公式ドキュメントでは、同じ層では、どちらか一方に統一するよう勧められています。両方に書くと、起動時に警告が出ます。ほかの設定と一緒に管理したいときはconfig.toml、フックだけを分けたいときはhooks.jsonが向いています。

AGENTS.mdとフックは、どう使い分けますか?

「守ってほしいことを文章で伝える」のはAGENTS.md、「条件に合えば必ず実行したい処理」はフックです。たとえば「コードの書き方の方針」はAGENTS.mdに書き、「テストが通っていなければ終わらせない」はフックで確実に動かします。

使えるイベントには、どんなものがありますか?

2026年10月時点で、PreToolUse、PermissionRequest、PostToolUse、PreCompact、PostCompact、UserPromptSubmit、SubagentStop、Stop、Interrupt、SessionStart、SubagentStart、SessionEndの12です。新しいイベントが追加されることもあるため、公式のページで最新を確認してください。

Pythonが入っていなくても、フックは使えますか?

使えます。フックは、標準入力でJSONを受け取り、標準出力か終了コードで結果を返せれば、どの言語で書いても構いません。シェルスクリプトやNode.jsなど、環境に入っているものを使えます。

フックでWeb検索を止められますか?

できません。Web検索のようなホスト型のツールは、PreToolUseとPostToolUseの対象外と、公式ドキュメントに書かれています。Web検索の可否は、Codexの設定側で管理します。

フックで、ファイルの内容を自動で整形できますか?

ファイルが編集された後のPostToolUseで、整形のコマンドを動かす使い方は考えられます。ただし、公式ドキュメントには、整形を想定した例は載っていません。整形のコマンドが、編集したファイルを別に書き換えることになるため、Codexが次に読むファイルの内容が変わる点に注意してください。

フックが原因でCodexが止まってしまったときは?

CLIの/hooksで、該当のフックを無効にするか、config.tomlの[features]にhooks = falseを書いて、フック全体を止めます。そのうえで、スクリプトを単体で動かして、出力と終了コードを確かめます。

アプリやVS Code拡張でも、フックは使えますか?

公式ドキュメントのフックのページには、アプリやIDE拡張での/hooksに当たる画面や、動作の違いについての記述が見当たりませんでした。確認できているのは、CLIの/hooksです。アプリや拡張で使う場合は、小さなフックで動作を確かめてください。

信頼したフックの、スクリプトの中身を変えたらどうなりますか?

信頼は、フックの定義(コマンドの書き方など)に対して記録されます。スクリプトの中身だけを変えたときの扱いは、公式ドキュメントに記述がありません。共有のリポジトリでは、スクリプトの変更もレビューの対象にしておきましょう。

信頼の確認を、毎回しなくてよい方法はありますか?

一度信頼すれば、同じ定義のフックは、次からは確認なしで動きます。出どころを別の方法で確かめている自動化向けには、--dangerously-bypass-hook-trustというオプションもありますが、名前のとおり危険なため、通常の作業では使いません。

Claude Codeのフックの設定を、そのまま使えますか?

そのままでは使えません。イベント名や出力の形に似た部分はありますが、設定ファイルの置き場所や、使えるイベント、信頼の確認の流れが異なります。この記事と公式ドキュメントで、Codex側の書き方を確かめてください。

会社で、全員に同じフックを配るにはどうすればよいですか?

リポジトリの.codex/hooks.jsonに入れて共有する方法と、管理者がrequirements.tomlで強制する方法があります。後者では、スクリプトの配布は、MDMなどの別の仕組みで行う必要があります。

まとめ

この記事のまとめ

  • Codexのフックは、Codexの動きの決まったタイミングで、自分のスクリプトやMCPツールを自動で動かす仕組み
  • AGENTS.mdは「伝える」もの、フックは「条件に合えば必ず実行される」もの。守らせたいことの強さで使い分ける
  • hooks.jsonかconfig.tomlの[hooks]に、イベント・matcher・ハンドラーの3段で書く。新しいフックは、/hooksで内容を確認して信頼してから動く
  • イベントは12種類。ブロックは、JSONの判断か終了コード2で返す。Stopではstop_hook_activeで無限ループを防ぐ
  • WindowsではcommandWindowsでpy -3などに読み替える。WSLは、Linux側のホームを見るため、設定の置き場所に注意する
  • フックは完全な強制の境界ではない。サンドボックスや承認の設定とあわせて、安全を守る

まずは、動いた記録をファイルに残すフックか、危険なコマンドを止めるフックを1つ書いて、/hooksで信頼し、動くところまで試すのがおすすめです。最初から多くのことをフックに任せるより、1つずつ増やす方が、原因を切り分けやすくなります。

チームでCodexを使うときは、どのフックを共有するか、管理者の設定でどこまで強制するかなど、決めることが増えます。INTERCREATE JAPANでは、Claude CodeやCodexの導入から、社内で使い続けられるルールや設定づくりまでをお手伝いしています。

この記事を書いた人

INTERCREATE JAPAN 編集部

Web制作、業務システムの開発、Claude Code・Codex の導入支援を行う INTERCREATE JAPAN の編集部です。実際の制作や導入の現場で確かめた手順をもとに、AIツールの使い方やホームページの運用について解説しています。

参考・出典