Claude Code の PreToolUse フックで Codex に安全審査させる
Claude Code が実行しようとするBashコマンドをOpenAI の Codex CLI に自動レビューさせる仕組みを構築しました。
この記事では実装の詳細とハマりポイントをまとめます。
やりたかったこと
Claude Code はコマンドを実行する前にユーザーへの承認を求めることがあります。
これを毎回手動で判断するのは面倒です。
かといって全部自動承認にするのはリスクがあります。
正直なところ本当に危険そうな操作だけ自身で操作して、それ以外は自動で承認してほしい。
そこで PreToolUse フック を使い、承認が必要な場面で Codex CLI に「このコマンドは安全か?」を判断させる仕組みを作ることにしました。
処理の流れ
Claude Code がコマンド実行を試みる → PreToolUse フック発火 → Codex CLI に安全審査を依頼 → Codex の判断に基づいて allow / deny を返す → 承認なしで処理が続行(または拒否)
構成
- Claude Code(2.1.69)
- OpenAI Codex CLI(0.107.0)
- Python 3(フックスクリプト)
実装
1. フックスクリプト
~/.claude/hooks/codex-review.py
import json import sys import subprocess # stdin を確実に EOF まで読み切る hook_input = json.loads(sys.stdin.buffer.read()) tool_name = hook_input.get("tool_name", "") tool_input = hook_input.get("tool_input", {}) command = tool_input.get("command", "") # 明らかに安全なコマンドはスキップする SAFE_COMMANDS = [ "ls", "cat", "pwd", "echo", "printf", "git status", "git log", "git diff", "git branch", "grep", "rg", "find", "head", "tail", "wc", "which", "whereis", "uname", "date", "tree", "jq", "sed", "awk", "npm run", "pnpm run", "yarn run", "tsc", "eslint", "prettier", "pytest", "vitest", "jest", ] # コマンドが安全なものから始まる場合は、Codexのレビューをスキップして許可する(前方一致) if any(command.startswith(safe + " ") for safe in SAFE_COMMANDS): print( json.dumps( { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Safe command, skipped Codex review", } } ) ) # Codex に安全審査を依頼 prompt = f""" 以下のツール呼び出しが安全かどうかを判断してください。 ツール: {tool_name} 内容: {json.dumps(tool_input, ensure_ascii=False)} 安全なら "ALLOW"、危険なら "DENY: 理由" とだけ答えてください。 """ result = subprocess.run( ["codex", "exec", "--skip-git-repo-check", prompt], capture_output=True, text=True, timeout=30, cwd=cwd ) if "ALLOW" in result.stdout: print(json.dumps({ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Codex reviewed and approved" } })) else: print(json.dumps({ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": result.stdout } })) sys.exit(0)
必要であればログファイルも吐き出すようにしておくと良いでしょう。
2. settings.json への登録
~/.claude/settings.json の hooks セクションに追加します。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python $HOME/.claude/hooks/codex-review.py" } ] } ] } }
環境によってはpythonではなくpython3などになると思います。ご自身の環境に合わせて書き換えてください。
ハマりポイント
実装中にいくつかのハマりポイントがありました。同じ問題に遭遇した人のために記録しておきます。
PermissionRequest フックが発火しない
最初は PermissionRequest フックを使おうとしていました。
これはユーザーへの承認ダイアログが表示される直前に発火するイベントです。
しかし、これはclaude codeをコマンドライン実行したときには発火しません。
PreToolUse はツール実行前に必ず発火するため、こちらを使うのが確実です。
claude -p "git pushしてください"でgit pushをmainブランチに対して発行する場合、 PreToolUse であればhookが呼び出されます。
そして、codexによって"mainに直接pushするなんて!"と怒られて止まります。(AGENTS.mdで許可していなければ)
sys.stdin.read() でブロッキングする
フックスクリプトが sys.stdin.read() でブロッキングしてタイムアウトしていました。
Claude Code はフック起動時に stdin を閉じて渡すため、sys.stdin.buffer.read() の方が確実に EOF まで読んで返ってきます。
# NG: ブロッキングする可能性がある hook_input = json.loads(sys.stdin.read()) # OK: EOF まで確実に読み切る hook_input = json.loads(sys.stdin.buffer.read())
codex exec が git リポジトリ外でエラーになる
フックはサブプロセスとして起動されるため作業ディレクトリが保証されていません。
codex exec はデフォルトで git リポジトリ内での実行を要求するため、--skip-git-repo-check フラグが必要です。
permissions.allow との関係
~/.claude/settings.json の permissions.allow に登録されているコマンドは即実行されますが、それでも PreToolUse フックは発火します。
まとめ
| 項目 | 内容 |
|---|---|
| フックの種類 | PreToolUse(PermissionRequest ではない) |
| stdin の読み方 | sys.stdin.buffer.read() |
| Codex の呼び方 | codex exec --skip-git-repo-check |
| 安全なコマンドの扱い | SAFE_COMMANDS リストで早期リターン |
Claude Code と Codex を組み合わせると、Claude が設計・実装を担い、Codex がセキュリティ審査を担うという役割分担ができます。
今回の実装はその第一歩として機能しています。
最近Gemini 3.1 Flash-Liteもリリースされたので、こういった用途に使えるのではないかと期待しています。

