創刊準備号 · 2026年秋English · RSS
AgentKoji

Codex CLIのhooks入門

Codex CLIのhooksの置き場所、/hooksでの信頼の手順、危険なBashを止めるPreToolUseの例と限界を説明します。

要点(TL;DR)

  • Codex の hooks は、エージェントのループの途中でスクリプトや MCP のツールを動かす仕組みです。最初から有効で、~/.codex/ と <repo>/.codex/ の hooks.json か config.toml の [hooks] から読み込まれ、当てはまる hook はすべて動きます。
  • managed 以外の hook は、/hooks で確認して信頼するまで動きません。信頼は hook の内容のハッシュに結びつくので、新しい hook や書き換えた hook は信頼し直すまで飛ばされます。プロジェクトの hook は、その .codex/ が信頼済みのときだけ読み込まれます。
  • PreToolUse の hook は、JSON で permissionDecision: "deny" を返すか、exit 2 で stderr に理由を書けばツールの呼び出しを止められます。WebSearch などのホスト型ツールは tool hooks を通らず、公式も hooks を「完全な強制の境界ではなく、便利なガードレール」と位置づけています。

Codex の hooks を使うと、ツールを動かす前、動かしたあと、ターンが終わるときなど、エージェントのループの決まった場所で自分のスクリプトや MCP のツールを動かせます。この記事では、hooks の仕組み、Codex が hooks を探す場所、動かす前に必要な信頼の手順、危険な Bash コマンドを止める最小の PreToolUse hook、PostToolUse と Stop の例、動かないときの確認項目、hooks の限界を順に説明します。内容はすべて公式の hooks のページで2026年11月3日に確かめたものです。以下の hook の例は、編集部では Codex で動かしていません。公式の書式に合わせていますが、動作は未検証です。

Codex の hooks とは

hooks は Codex の拡張の仕組みで、エージェントのループの途中でスクリプトや MCP のツールを動かせます。公式は使い道として、会話をログや分析の基盤に送る、プロンプトに API キーが貼られていないか調べる、ターンが終わるときに検証を走らせる、などを挙げています。

hooks は最初から有効です。動き方には、次の3つの決まりがあります。

イベントは次のとおりです。

いつイベント
ターンの途中PreToolUse、PermissionRequest、PostToolUse、PreCompact、PostCompact、UserPromptSubmit、SubagentStop、Stop
モデルへのリクエストのたびPreModelRequest、PostModelRequest
動いているターンを中断したときInterrupt(サブエージェントでは動かない)
セッションやサブエージェントが始まるときSessionStart、SubagentStart
メインのスレッドが終わるときSessionEnd(サブエージェントでは動かない)

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

Claude Code の hooks を知っている人へ

見覚えのあるところもあります。PreToolUse、PostToolUse、UserPromptSubmit、Stop といったイベント名を使い、PreToolUse の hook は hookSpecificOutput.permissionDecision で拒否でき、exit code 2 で stderr に理由を書けば止められます。Claude Code の hook をそのまま持ってくる前に、Codex に特有の点を3つ押さえておきます。

比べたのは両方の公式ドキュメントに書いてあることだけで、Claude Code 用の hook のスクリプトが Codex で同じように動くかは試していません。Claude Code 側は「Claude Code hooks実践」と「Claude Codeのhooks早見表」にまとめています。

Codex が hooks を探す場所

Codex は、有効な設定のレイヤーの隣にある hooks.json か、config.toml の中の [hooks] テーブルから hooks を読み込みます。実用的なのは次の4か所です。

複数あれば、当てはまる hook はすべて読み込まれます。優先度の高いレイヤーが、低いレイヤーの hook を置き換えることはありません。1つのレイヤーに hooks.json と [hooks] の両方があると、Codex は両方をまとめたうえで起動時に警告を出すので、1つのレイヤーではどちらか一方にします。有効にしたプラグインが hooks を持ち込むこともあり、それも同じ信頼の手順を通ります。

プロジェクトの hook は、そのプロジェクトの .codex/ が信頼済みのときだけ読み込まれます。 信頼されていないプロジェクトでも、ユーザーやシステムの hook は読み込まれます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

hook を確認して信頼する

managed 以外の hook を動かすには、その定義をそのまま確認して信頼する必要があります。Codex は信頼を hook のその時点のハッシュに結びつけて記録するので、新しい hook や書き換えた hook は確認待ちになり、信頼するまで飛ばされます。つまり、信頼済みの hook を書き換えると、確認し直すまで動かなくなります。

CLI で /hooks を開くと、hook の出どころを見る、新しい hook や書き換えた hook を確認する、信頼する、managed 以外の hook を1つずつ無効にする、といった操作ができます。起動時に確認待ちの hook があれば、Codex は /hooks を開くよう警告を出します。システム、MDM、クラウド、requirements.toml から来る managed の hook はポリシーで信頼済みとされ、この画面からは無効にできません。

別の方法で hook の出どころを確かめている一回限りの自動化では、--dangerously-bypass-hook-trust を付けると、その実行に限って信頼の記録なしで有効な hook を動かせます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

危険な Bash コマンドを止める最小の PreToolUse hook

hook は3段の構造です。イベント、そのイベントに当てはまるかを決める matcher のグループ、当てはまったときに動く1つ以上のハンドラーです。<repo>/.codex/hooks.json に次のように書きます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",
            "timeout": 30,
            "statusMessage": "Checking Bash command"
          }
        ]
      }
    ]
  }
}

コマンドはセッションの作業ディレクトリで実行され、Codex はサブディレクトリから起動されることもあります。そのため公式は、リポジトリ内のスクリプトを上のように git のルートから解決するよう勧めています。同じ hook を config.toml に書くと次のとおりです。

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

[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py"'
timeout = 30
statusMessage = "Checking Bash command"

command hook は、stdin で JSON を1つ受け取ります。Bash の場合、コマンドは tool_input.command に入っています。stdout の素のテキストは無視されるので、スクリプトは JSON で答えます。.codex/hooks/pre_tool_use_policy.py として保存します(未検証の例です)。

#!/usr/bin/env python3
"""PreToolUse hook: deny Bash commands that match a blocklist. Unverified example."""
import json
import re
import sys

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

# Patterns are examples; adjust them to your repository's policy.
BLOCKED = [
    (r"\brm\s+-\w*r\w*f|\brm\s+-\w*f\w*r", "recursive forced delete"),
    (r"\bgit\s+push\b.*\s(--force|-f)\b", "force push"),
]

for pattern, label in BLOCKED:
    if re.search(pattern, command):
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": f"Blocked by repository policy: {label}.",
            }
        }))
        sys.exit(0)

sys.exit(0)

何も出力せずに exit 0 で終われば成功とみなされ、ツールの呼び出しはそのまま進みます。JSON の代わりに、exit code 2 で終わって stderr に理由を書いても止められます。古い形の {"decision": "block", "reason": "..."} も受け付けられます。

PreToolUse から permissionDecision: "ask"、continue: false、stopReason、suppressOutput を返してはいけません。読み取られはしますが、まだ対応していません。Codex はその hook の実行を失敗として扱い、ツールの呼び出しを続けます。また、この例のような正規表現の拒否リストはすり抜けられやすいので、気にしている入力で必ず試してください。拒否のパターンの例は「PreToolUse hookの例集」に、編集部の最初の git のガードに見つかった穴は「Claude Code hooks実践」にあります。

設定したら、そのリポジトリで Codex を起動し、/hooks を開いて新しい hook を確認し、信頼します。信頼するまで、この hook は飛ばされます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

PostToolUse と Stop の使いどころ

PostToolUse は、対応するツールが出力を返したあとに動きます。exit が0以外で終わった Bash のコマンドのあとにも動きます。ツールがしたことを取り消すことはできません。decision: "block" を返すか exit 2 で終わると、ツールの結果があなたのフィードバックに置き換えられ、モデルはそこから続けます。たとえば、コマンドが自動生成のファイルを書き換えたときに知らせる使い方ができます(未検証の例です)。

{
  "decision": "block",
  "reason": "The command changed generated files. Regenerate them instead of editing by hand.",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Files under gen/ are generated."
  }
}

Stop はターンが終わるときに動き、matcher は無視されます。exit 0 で終わるときは JSON を出力する必要があり、素のテキストは無効です。decision: "block" はターンを拒否するのではなく、Codex に作業を続けさせ、reason を新しいプロンプトとして送ります。「終える前にテストを走らせる」といった確認に向いています(未検証の例です)。

{
  "decision": "block",
  "reason": "Run one more pass over the failing tests."
}

入力には、このターンがすでに Stop で続けられたかを示す stop_hook_active があります。hook がターンをいつまでも続けさせないよう、これを確かめます。当てはまる Stop の hook のどれかが continue: false を返すと、ほかの hook の「続ける」という判断より優先されます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

hook が動かないとき

次の順に確かめます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

Claude Code 側の同じような確認項目は「hooksが動かないときの確認」にまとめています。

hooks の限界

PreToolUse と PostToolUse が対象にするのは、シェルのコマンド、apply_patch、MCP のツール、そのほかほとんどのローカルの関数ツールです。WebSearch などのホスト型ツールはローカルの関数ツールの経路を通らないので、tool hooks からは見えません。専用の経路を持つツールが、既定の hook の経路から外れることもあります。公式は、tool hooks を完全な強制の境界ではなく、便利なガードレールとして扱うよう書いています。

同じページから、限界をあと2つ挙げます。

出典:https://developers.openai.com/codex/hooks 、2026-11-03確認

Codex CLI をまだ入れていない人は「Codex CLIの始め方」から始めてください。

まとめ

関連記事

よくある質問

Codex CLIのhooksはどこに書く?
hooks.json か、config.toml の中の [hooks] テーブルに書きます。公式が実用的な場所として挙げているのは、~/.codex/hooks.json、~/.codex/config.toml、<repo>/.codex/hooks.json、<repo>/.codex/config.toml の4か所です。複数あれば、当てはまる hook はすべて読み込まれます。
Codexのhookが動かないのはなぜ?
よくある原因は、まだ信頼していない hook(新しい hook や書き換えた hook は /hooks で信頼するまで飛ばされます)、信頼されていないプロジェクトの .codex/、ツール名に合っていないかそのイベントでは無視される matcher、timeout、[features] hooks = false による無効化です。
CodexのPreToolUse hookでコマンドを止めるには?
stdout に hookSpecificOutput.permissionDecision を "deny" にした JSON を permissionDecisionReason 付きで返すか、exit code 2 で終わって stderr に理由を書きます。stdout の素のテキストは無視されます。
Codexのhooksはセキュリティの境界になる?
なりません。WebSearch などのホスト型ツールは tool hooks の経路を通らず、専用の経路を持つツールも一部あります。公式は、tool hooks を完全な強制の境界ではなく、便利なガードレールとして扱うよう書いています。
Codexのhookはどのくらいの時間動ける?
timeout の単位は秒です。省略すると、ほとんどの hook は600秒です。SessionEnd と Interrupt は既定が1秒で、最大3秒まで設定できます。