Govern OpenAI Codex CLI with Agentic Control Plane
OpenAI Codex CLI ships a Claude-style hook system that’s gated behind a feature flag and currently only intercepts the Bash tool. To govern Codex end-to-end you want both the hook (Path 1) and the MCP connector (Path 2) — hook handles Bash, MCP covers Read, Edit, Write, web fetch, and any tool you’ve configured via [mcp_servers.*] in config.toml.
TL;DR
curl -sf https://agenticcontrolplane.com/install.sh | bash
The installer detects Codex (command -v codex), enables [features].codex_hooks = true in ~/.codex/config.toml, writes ~/.codex/hooks.json with PreToolUse + PostToolUse entries pointing at ~/.acp/govern.mjs, and adds acp as an entry under [mcp_servers.acp]. The hook governs Bash; the MCP connector governs everything else routed through MCP.
Important: Codex hooks are off by default
OpenAI’s Codex hook engine is marked Stage::UnderDevelopment in the Codex source. It works — full Claude-style PreToolUse and PostToolUse — but you have to opt in by adding to ~/.codex/config.toml:
[features]
codex_hooks = true
The ACP installer will do this for you and prompt before writing, so you know what’s changing. Without the flag, hooks are silently ignored — no error, no log line, just no governance. If you’ve enabled hooks and they’re not firing, this flag is the first thing to check.
How it works
ACP installs at three layers, designed to compose:
- Hook layer (Path 1) —
~/.codex/hooks.jsonregistersPreToolUseandPostToolUseevents pointing at~/.acp/govern.mjs. Same script as Claude Code; samepermissionDecision: "deny"semantics. - MCP connector (Path 2) —
[mcp_servers.acp]in~/.codex/config.tomlpoints Codex atmcp.agenticcontrolplane.com/mcp. Codex routes any tool call configured to use that MCP server through ACP. - Audit layer — every Codex tool call (whether intercepted by hook or MCP) lands in your ACP activity log with
client.name: "codex", theturn_id, the subagent context (Codex’sSessionSourceis finer-grained than Claude Code’s tier), and the full delegation provenance.
What gets installed and where
| Path | Purpose |
|---|---|
~/.acp/govern.mjs |
Hook script — shared with Claude Code/Cursor installs |
~/.acp/credentials |
Bearer token from browser OAuth |
~/.codex/config.toml |
Adds [features].codex_hooks = true and [mcp_servers.acp] block |
~/.codex/hooks.json |
Adds PreToolUse + PostToolUse entries under hooks.PreToolUse[] and hooks.PostToolUse[] |
The installer is idempotent. Running it again on a machine where Codex already has the entries will skip them. Running on a machine without Codex installed is a no-op for the Codex section.
Limitations — read this before relying on Codex hooks alone
This is the section that matters most. Codex hooks are powerful but partial.
PreToolUse only intercepts the Bash tool
Per OpenAI’s Codex hooks documentation: “Currently PreToolUse only supports Bash tool interception.” Read, Write, Edit, Apply Patch, web fetch, and MCP tool calls do not fire PreToolUse.
This is why ACP installs both the hook and the MCP connector. The hook handles Bash (where most of the destructive-action risk lives — rm -rf, gh repo delete, psql DROP TABLE). The MCP connector handles everything routed through MCP servers, which is where Codex’s file edits and structured tool calls increasingly live.
If you don’t have any tools wired through MCP and you only care about governing Bash, the hook alone is enough.
Tool input/output mutation is not yet supported
Codex’s hook engine parses but rejects updatedInput (PreToolUse) and updatedMCPToolOutput (PostToolUse). Specifically:
"PreToolUse hook returned unsupported updatedInput"— hooks-output-parser.rs:259"PostToolUse hook returned unsupported updatedMCPToolOutput"— hooks-output-parser.rs:250
You can observe and deny today; you cannot redact or rewrite. The MCP connector path supports both — another reason to wire both layers.
Only permissionDecision: "deny" is operational
PreToolUse parses but rejects "allow" and "ask" decisions. The only response that does anything is deny with a permissionDecisionReason. Effectively the only output shape your hook should emit.
type: "command" is the only working handler
Codex’s schema also defines prompt and agent handler types but they parse-and-warn-skip. Use command exclusively.
Hooks merge across config layers without namespacing
~/.codex/hooks.json and <repo>/.codex/hooks.json both load. There’s no marker to identify ACP-owned entries. The installer uses a stable statusMessage: "ACP policy check" string so it can find and idempotently update its own entries on re-install. If you hand-edit hooks, leave that statusMessage alone.
Schema is evolving fast
Codex ships multiple releases per day (75K stars, 410 contributors). The wire types use deny_unknown_fields so additive changes can break strict parsers. ACP pins to tested Codex versions and updates the adapter when fields land. If you’re on bleeding-edge Codex (alpha releases), things may shift under you.
What you’ll see in the dashboard
Once Codex is governed, cloud.agenticcontrolplane.com/agents shows a codex row with activity broken down by Codex’s session source — cli, vscode, exec, appServer, subAgent (with the SubAgentSource subtype: Review, ThreadSpawn, MemoryConsolidation, Compact). Codex exposes finer-grained subagent metadata than Claude Code does — including parent_thread_id and depth for ThreadSpawn — which gives you better A2A signal automatically.
Setting up policy
The same three-axis model applies (Tool / Agent / User policies). Specifically for Codex:
- Tool policies — restrict the Bash subcommands Codex can run (
Bash.git,Bash.npm,Bash.docker— already classified server-side). - Agent policies — limit which session sources can run high-risk operations.
subAgent.MemoryConsolidationshould probably never delete files, for example. - User policies — gate Codex enterprise installs to specific identities.
Troubleshooting
Hook isn’t firing. Check ~/.codex/config.toml has [features].codex_hooks = true. The flag is off by default and hooks are silent without it.
Hook fires for Bash but not for Read/Edit. Expected — see Limitations. Use the MCP connector for non-Bash tools.
Codex says “ACP policy check timed out”. Default hook timeout is 30 seconds in hooks.json; if you’ve shortened it, ACP fail-closed behavior denies the call. Increase or accept the deny.
updatedInput rejection error in Codex logs. Don’t return updatedInput from PreToolUse — only permissionDecision: "deny" with a reason. Codex’s parser strict-rejects unsupported fields.
Multiple hook layers conflicting. Both user (~/.codex/) and repo (<repo>/.codex/) hooks load and merge. Repo-level entries override user-level for matching tools. Inspect the merged config with codex --debug-hooks (alpha command).
Related integrations
- Claude Code — same hook pattern, broader tool coverage (PreToolUse fires for all tools, not just Bash)
- Cursor — different IDE, hook-based governance
- OpenAI Agents SDK — for building OpenAI-model-backed agents with full A2A delegation chains
- Agent-to-Agent governance — how delegation chains carry identity through Codex’s
SubAgentSourcehops