Codex Hooks(フック)とは?設定方法・イベント・使い方の例を解説
この記事でわかること
- フックとは何か、AGENTS.md・スキル・ルールとの違い
- hooks.jsonとconfig.tomlの書き方、置き場所、最初のフックを動かす手順
- 12のイベントとmatcher、入力と出力(終了コード・JSON)のしくみ
- 危険なコマンドの阻止・APIキーの混入防止・テスト結果の確認など、よく使う例
- Windowsでの書き方(commandWindows)とWSLの注意
- 信頼の確認・無効化・管理者設定など、安全に使うための注意
目次
- Codex Hooksとは
- フックでできること
- AGENTS.md・スキル・ルールとの違い
- Claude Codeのフックとの関係
- フックでできないこと・向かないこと
- 最初のフックを設定する
- 設定ファイルの置き場所
- 設定ファイルの形
- よく使う設定項目
- 手順:最初のフックを動かす
- 信頼の確認(/hooks)
- イベント・入力・出力のしくみ
- イベント:動くタイミング
- matcher:どんなときに動かすか
- フックが見られるツールの範囲
- フックが受け取る入力
- フックが返す出力:終了コードとJSON
- イベント別:ブロックと続きの作業のさせ方
- 追加の情報が大きすぎるとき
- よく使う例
- 危険なコマンドを止める(PreToolUse)
- 編集させないファイルを守る(PreToolUse)
- APIキーの貼り付けを防ぐ(UserPromptSubmit)
- 終わる前にテストを確認する(Stop)
- 作業メモを自動で渡す(SessionStart)
- 実行の記録を残す(PostToolUse)
- 承認を自動で判断する(PermissionRequest)
- フォルダごとに追加の指示を足す
- 複数のフックを1つのファイルにまとめる
- 使い分けの目安
- Windowsでの書き方
- Windows専用のコマンドを書く(commandWindows)
- Codexのホームフォルダと、WSLの注意
- 公式に書かれていないこと
- MCPツール・非同期・プラグイン・管理者設定
- MCPツールを呼ぶフック
- バックグラウンドで動かす(async)
- プラグインに同梱する
- 管理者が配る(requirements.toml)
- 安全に使う・動かないとき
- 安全に使うための基本
- 手元でスクリプトを試す
- フックを無効にする
- 動かないときの確認ポイント
- よくある質問
- まとめ
「テストを実行してから終わって」と毎回頼んでいるのに、たまに忘れられる。「rm -rfだけは絶対に動かしてほしくない」。「チャットにAPIキーを貼り付けてしまわないか心配だ」。Codexを仕事で使い込むほど、こうした悩みが出てきます。
これらを解決するのが、Codexの「フック(Hooks)」です。フックは、Codexの動きの決まったタイミングで、自分で用意したスクリプトを自動的に実行させる機能です。AGENTS.mdのように「お願い」を書くのではなく、条件が合えば毎回必ず動くところが大きな違いです。
この記事では、フックの考え方から、設定ファイルの書き方、イベント・入力・出力のしくみ、よく使う例、Windowsでの書き方、安全に使うための注意までを順に解説します。内容は2026年10月時点の公式ドキュメントをもとにしています。フックは機能の追加が続いているため、細かい仕様は公式ページもあわせて確認してください。
Codex Hooksとは

フックは、Codexの「拡張の仕組み」のひとつです。公式ドキュメントでは、Codexがユーザーの依頼を受けて動いている間(エージェントのループ)に、スクリプトやMCPツールを実行できる仕組みだと説明されています。
「ツールを実行する直前」「応答を終える直前」のように、Codexの動きの途中に、自分の処理を差し込めると考えると分かりやすいでしょう。差し込んだ処理が結果を返すと、Codexはそれを見て、動作を止めたり、続きの作業をしたりします。
フックでできること
公式ドキュメントでは、フックを使うと次のような機能を作れると紹介されています。
- チャットの内容を、自分たちのログ・分析の仕組みに送る
- チームのプロンプトを検査して、APIキーを誤って貼り付けていないかを調べ、ブロックする
- チャットを要約して、記憶として自動的に残す
- ターンが終わったときに独自の検証を実行し、基準を満たしていなければ続きをやらせる
- 特定のフォルダで作業するときだけ、指示を追加する
具体的な設定は、のちほど実用例として紹介します。まずは、フックがほかの仕組みとどう違うのかを整理しておきます。
AGENTS.md・スキル・ルールとの違い
Codexには、動きを調整する仕組みがいくつかあります。フックは、「条件が合えば必ず実行される」という点で、ほかと性質が異なります。
| 仕組み | 何をするものか | 確実に実行されるか |
|---|---|---|
| AGENTS.md | プロジェクトのルールや前提を、文章でCodexに伝える指示書 | Codexが読んで従う。守られるかはモデルの判断 |
| スキル | 作業の手順や専門知識をまとめ、必要なときに読み込ませる | Codexが必要と判断したときに使う |
| ルール | サンドボックスの外でCodexが実行してよいコマンドを制御する(公式では実験的な機能) | コマンドの実行可否を決める |
| フック | Codexの動きの決まったタイミングで、自分のスクリプトやMCPツールを動かす | 条件に合えば、そのたびに実行される |
たとえば「本番用のファイルは触らないで」とAGENTS.mdに書いても、Codexがそれを読み落とすことはありえます。フックなら、ファイルを書き換える操作そのものを検査して止められます。反対に、「見出しは短くして」のような文章の好みは、フックではなくAGENTS.mdに書く方が向いています。
Claude Codeのフックとの関係
Claude Codeにも「フック」という同じ名前の機能があります。イベントの名前(PreToolUse、Stopなど)や、JSONで結果を返す形は、Claude Codeを使ったことがある方にはなじみのあるものです。公式ドキュメントでも、プラグインのフックには、既存のものと合わせられるようCLAUDE_PLUGIN_ROOTなどの環境変数が用意されると説明されています。
ただし、設定ファイルの置き場所、使えるイベント、「信頼の確認」の流れなどは異なります。Claude Code側の書き方をそのまま持ち込まず、この記事の内容か公式ドキュメントで確かめてください。Claude Codeのフックは、別の記事で詳しく解説しています。
フックでできないこと・向かないこと
フックは便利ですが、公式ドキュメントにも次のような注意があります。
- ツールのフックは、「便利な安全装置」であって、完全な強制の境界ではありません。一部の特殊なツールの経路は、標準のフックの経路を通らないことがあります
- ホスト型のツール(Web検索など)は、
PreToolUseとPostToolUseの対象外です PostToolUseは、すでに実行されたコマンドの副作用を取り消せません
そのため、絶対に守りたいことは、フックだけに頼らず、サンドボックスや承認の設定とあわせて守る形にします。詳しくは「安全に使う・動かないとき」で説明します。
最初のフックを設定する

フックは、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段の入れ子になっています。
- イベントを選ぶどのタイミングで動かすか。
PreToolUse(ツールの実行前)やStop(ターンの終了時)などです - matcher(条件)を決めるそのイベントのうち、どんな場合だけ動かすか。「Bashのときだけ」のように絞ります。省略するとすべてに合います
- ハンドラー(実行する処理)を書く動かすコマンドなどを指定します。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は制限なし |
commandWindows | Windowsだけで使うコマンド | TOMLではcommand_windowsも可 |
公式ドキュメントによると、ハンドラーの種類として使えるのはcommandとmcp_toolです。promptとagentは「解析はされるが、実行はスキップされる」とあり、今のところ動きません。
リポジトリ内のスクリプトを呼ぶときは、.codex/hooks/...のような相対パスより、Gitのルートから解決するパスが勧められています。Codexをサブフォルダから起動することがあるためです。公式の例では、"$(git rev-parse --show-toplevel)/.codex/hooks/..."のような書き方が使われています。
手順:最初のフックを動かす
ここでは、「Bashのコマンドを実行する前に、危険なコマンドを止める」フックを例に、設定から動作確認までを説明します。スクリプトの中身は、「よく使う例」で紹介するものを使います。
- スクリプトを置く
~/.codex/hooks/の下に、pre_tool_use_policy.pyを保存します。Pythonが入っていない場合は、ほかの言語でも構いません。標準入力でJSONを受け取って、標準出力に返せればよいためです hooks.jsonを書く上の「設定ファイルの形」の例を、~/.codex/hooks.jsonとして保存します- Codexを起動し直す新しいフックは、起動時に読み込まれます。レビューが必要なフックがあると、起動時に
/hooksを開くよう警告が出ます /hooksで内容を確認して信頼するCLIで/hooksと入力すると、フックのブラウザが開きます。イベントを選んで、中身を確認し、問題がなければ信頼します(次の節で説明します)- 動作を確認する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 | ツールを実行する直前 | 実行を拒否する。入力を書き換えて実行させる。追加の情報を渡す |
PermissionRequest | Codexが承認を求める直前 | 承認する・拒否する・判断しない(通常の承認画面に任せる) |
PostToolUse | ツールが出力を返した直後 | 結果を差し替えるフィードバックを返す。追加の情報を渡す。取り消しはできない |
PreCompact | 会話の圧縮(コンパクト)の直前 | 圧縮を止める |
PostCompact | 会話の圧縮の直後 | 圧縮の後で処理を止める |
SubagentStart | サブエージェントの開始時 | サブエージェントに追加の情報を渡す(開始は止められない) |
SubagentStop | サブエージェントの終了時 | もう一度やり直させる |
Stop | ターンの終了時 | 続きの作業をさせる |
Interrupt | ターンを中断したとき | 記録や後片付け。中断自体は止められない |
SessionEnd | メインのスレッドが終わるとき | 最終メモの保存や後片付け。助言的な扱いで、Codexの動きは変えられない |
SessionEndは、開いたままの会話をアーカイブまたは削除したとき、Codexが通常どおり終了したとき、会話がどの接続中のクライアントでも開かれないまま30分たったときに動きます。ほかの会話に切り替えただけでは、すぐには動きません。
SessionStartは、圧縮の後にも動きます。自動圧縮がターンの途中で起きた場合は、追加の情報が、その続きの最初のモデルへのリクエストに渡されます。圧縮で消えてしまった前提を、もう一度渡す使い方ができます。
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は、シェルコマンドだけでなく、ほとんどのローカルの関数ツールで動きます。公式ドキュメントの整理は次のとおりです。
| ツールの経路 | PreToolUse | PostToolUse | matcherの名前 |
|---|---|---|---|
| シェルコマンド | ○ | ○ | 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のいずれか)も入ります。
そのうえで、イベントごとに次のような項目が加わります。
| イベント | 加わる主な項目 |
|---|---|
SessionStart | source(startup・resume・clear・compact・fork) |
UserPromptSubmit | prompt(送ろうとしているプロンプト) |
PreToolUse | tool_name、tool_use_id、tool_input(Bashとapply_patchではtool_input.command) |
PermissionRequest | tool_name、tool_input(tool_input.descriptionに承認の理由が入ることがある) |
PostToolUse | tool_name、tool_use_id、tool_input、tool_response(ツールの出力) |
Stop | stop_hook_active(すでにStopで続きの作業をさせたターンか)、last_assistant_message(最後の応答の文章) |
SubagentStart・SubagentStop | agent_id、agent_type。Stopにはagent_transcript_path、last_assistant_messageなども |
PreCompact・PostCompact | trigger(manualかauto) |
SessionEnd | reason(今のところ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(制限なし)は、出力の量を自分で厳しく抑えているフック以外では使わないよう、注意書きがあります。
もうひとつ、重要な注意があります。大きな出力はディスクに保存されることがあるため、フックの出力に、秘密情報やパスワードなどを含めないようにしてください。
よく使う例

ここでは、実際に使いやすい例を紹介します。スクリプトは、この記事のために作った例で、公式ドキュメントに載っているものではありません。入力と出力の形は公式ドキュメントに沿わせ、手元の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での書き方

公式ドキュメントのフックの例は、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側の~/.codex | Linux側の~/.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を付けて、フックが動いている表示を確認し、標準エラーに短い文章を出すだけのスクリプトから始めると、原因を切り分けやすくなります。
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ツールのフックは使えません
バックグラウンドで動かす(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_dir | macOSとLinuxで、管理されたフックのスクリプトを置くフォルダ(絶対パス) |
windows_managed_dir | Windowsでの、同じフォルダ(絶対パス) |
[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があります。フック名、設定の出どころ、成否ごとに集計できるため、組織で運用するときの確認に役立ちます。
よくある質問
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