# Codex CLI Permissions, Hooks & Audit Log — One-Command Install

Set per-command permissions for OpenAI Codex CLI in one command. Hooks cover Bash; the MCP connector covers every other tool. Full audit log, policy, and per-user attribution.

# Govern OpenAI Codex CLI with Agentic Control Plane

<p style="font-size:17px;line-height:1.6;color:var(--acp-text);max-width:660px;margin:8px 0 6px;">See, control, and price <strong>every tool call Codex makes</strong> — the shell commands, the file edits, the model calls — from one dashboard. One command, no code changes, first governed call in about 30 seconds.</p>

## TL;DR

```bash
# Free, on-device, no account — governs Codex's shell commands locally:
curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local

# Or connect a workspace for full non-Bash coverage, team policy, and cost X-ray:
curl -sf https://agenticcontrolplane.com/install.sh | bash
```
<p style="font-size:12.5px;color:var(--acp-text-faint);margin:8px 0 0;">MIT-licensed and auditable — <a href="#verify-the-installer">read it before you run it</a>.</p>

<p style="font-size:13.5px;line-height:1.55;color:var(--acp-text);max-width:660px;margin:12px 0 0;"><strong>Local vs cloud for Codex.</strong> In <code>--local</code> mode the hook governs Codex's <strong>Bash</strong> calls on-device (decisions from <code>~/.acp/policy.json</code>, logged to <code>~/.acp/audit.jsonl</code>) — but the MCP connector that covers Read/Edit/Write/web-fetch is <strong>cloud-only</strong> and is skipped locally, because non-Bash Codex tools aren't hookable yet and are governed through the ACP MCP endpoint. For end-to-end Codex coverage, run without <code>--local</code>.</p>





OpenAI Codex CLI ships a Claude-style hook system — stable and enabled by default from 0.145.0 onward, where the original `codex_hooks` flag (under development, off by default) was renamed to `hooks` — but its hook events still only intercept 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`.


The installer detects Codex (`command -v codex` or `~/.codex/` exists), then wires **three layers in one command**:

1. **Hooks** — Enables `[features].codex_hooks = true` in `~/.codex/config.toml`, writes `~/.codex/hooks.json` with PreToolUse + PostToolUse entries pointing at `~/.acp/govern.mjs`. Governs every Bash call Codex makes.
2. **MCP connector** — Adds `[mcp_servers.acp]` to `~/.codex/config.toml` so non-Bash tools (Read, Edit, Write, web fetch, custom MCP servers) flow through the ACP MCP endpoint.
3. **AGENTS.md directive** — Writes an ACP-governance section to `~/.codex/AGENTS.md` instructing Codex to call `acp_check` before non-Bash tool calls (instruction-layer enforcement for the gap between what hooks cover and what policy needs).

The MCP block uses a shell substitution (`sh -c ... Bearer $(cat ~/.acp/credentials)`) so the Authorization header reads your credentials file every time Codex starts the server. No install-time API key needed, and credential rotation just means overwriting the file.

## Important: Codex hooks are off by default

OpenAI's Codex hook engine is marked `Stage::UnderDevelopment` in the [Codex source](https://github.com/openai/codex/blob/main/codex-rs/features/src/lib.rs). It works — full Claude-style PreToolUse and PostToolUse — but you have to opt in by adding to `~/.codex/config.toml`:

```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:

1. **Hook layer** (Path 1) — `~/.codex/hooks.json` registers `PreToolUse` and `PostToolUse` events pointing at `~/.acp/govern.mjs`. Same script as Claude Code; same `permissionDecision: "deny"` semantics.
2. **MCP connector** (Path 2) — `[mcp_servers.acp]` in `~/.codex/config.toml` points Codex at `mcp.agenticcontrolplane.com/mcp`. Codex routes any tool call configured to use that MCP server through ACP.
3. **Audit layer** — every Codex tool call (whether intercepted by hook or MCP) lands in your ACP activity log with `client.name: "codex"`, the `turn_id`, the subagent context (Codex's `SessionSource` is 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 (written after auth) |
| `~/.codex/config.toml` | Adds `[features].codex_hooks = true` and `[mcp_servers.acp]` block |
| `~/.codex/hooks.json` | Adds PreToolUse + PostToolUse entries registering `node $HOME/.acp/govern.mjs` |

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.

## How MCP coverage works (no API-key-at-install-time needed)

Codex's `PreToolUse` hook only intercepts the Bash tool. Non-Bash tools (Read, Edit, Write, web fetch, etc.) are covered by the MCP connector the installer also wires.

The `[mcp_servers.acp]` block the installer writes looks like:

```toml
[mcp_servers.acp]
command = "sh"
args = ["-c", 'exec npx -y mcp-remote https://mcp.agenticcontrolplane.com/mcp --header "Authorization: Bearer $(cat ~/.acp/credentials)"']
```

The `sh -c` wrapper is load-bearing: it evaluates `$(cat ~/.acp/credentials)` every time Codex launches the MCP server, which means:

- No API key is required at install time (hooks install → browser auth → credentials saved → first Codex restart picks it up)
- Credential rotation is a one-step operation — overwrite `~/.acp/credentials` and restart Codex
- The literal API key never appears in `config.toml`, which is safer for dotfile checkins

## 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](https://developers.openai.com/codex/hooks): *"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](https://github.com/openai/codex/blob/main/codex-rs/hooks/src/engine/output_parser.rs)
- `"PostToolUse hook returned unsupported updatedMCPToolOutput"` — [hooks-output-parser.rs:250](https://github.com/openai/codex/blob/main/codex-rs/hooks/src/engine/output_parser.rs)

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, [your console](https://cloud.agenticcontrolplane.com/login?from=%2Fonboarding&surface=codex){: data-track="CodexIntegration: Console Signup"} 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.MemoryConsolidation` should 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`. A timed-out check follows your fail mode — allowed-but-flagged-ungoverned by default, denied if you've opted into fail-closed (`echo closed > ~/.acp/failmode`).

**`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).

## Add the cost X-ray

The MCP coverage above governs and audits **tool** calls — it doesn't see the model's token usage, so it can't show cost. To meter spend, prompt-cache hit rate, and the loop-vs-leaf cost X-ray, route Codex's model calls through the ACP proxy: point Codex's model provider `base_url` at `https://api.agenticcontrolplane.com/openai/v1`, set `requires_openai_auth = true`, and send your ACP key (`gsk_...`) separately as `x-acp-key`. Codex then preserves your existing ChatGPT subscription or API-key login; ACP governs the tool-use blocks the model emits as a second layer without silently changing who pays.

Full walkthrough: [Turn on Cost X-Ray](/cost-tracking).

## Verify the installer
{: #verify-the-installer}

The installer is open source — read every line before you run it.

<div style="margin:20px 0 28px;padding:16px 20px;border:1px solid #d0d0e8;border-radius:10px;background:#f0effe;font-size:13px;line-height:1.7;">
  <strong style="color:#5b5bd6;">Review before you pipe to bash:</strong><br/>
  <span style="color:#4a4a52;">· Source code: <a href="https://github.com/agentic-control-plane/acp-install" style="color:#5b5bd6;">github.com/agentic-control-plane/acp-install</a> (MIT, auditable)</span><br/>
  <span style="color:#4a4a52;">· SHA-256: <a href="/install.sh.sha256" style="color:#5b5bd6;"><code style="font-size:12px;">/install.sh.sha256</code></a> (compare with GitHub to verify you got the published version)</span><br/>
  <span style="color:#4a4a52;">· Dry read: <code style="font-size:12px;">curl -sf https://agenticcontrolplane.com/install.sh | less</code> — see every line before running</span><br/>
  <span style="color:#4a4a52;">· License: MIT · Issues: <a href="https://github.com/agentic-control-plane/acp-install/issues" style="color:#5b5bd6;">github</a></span>
</div>

## Frequently asked questions



## Related integrations

- [Codex CLI hooks reference](/blog/codex-cli-hooks-reference) — the deep reference: hooks.json, payload contract, deny semantics, `--full-auto` behavior, and every known limitation
- [Claude Code](/integrations/claude-code) — same hook pattern, broader tool coverage (PreToolUse fires for all tools, not just Bash)
- [Cursor](/integrations/cursor) — different IDE, hook-based governance
- [OpenAI Agents SDK](/integrations/openai-agents-sdk) — for building OpenAI-model-backed agents with full A2A delegation chains
- [Agent-to-Agent governance](/agent-to-agent) — how delegation chains carry identity through Codex's `SubAgentSource` hops

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Install Agentic Control Plane in OpenAI Codex CLI",
  "description": "Install ACP hooks and MCP connector in OpenAI Codex CLI for end-to-end governance.",
  "totalTime": "PT2M",
  "step": [
    {"@type": "HowToStep", "name": "Run the installer", "text": "Free and on-device, no account: curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local . The hook governs Codex's Bash calls locally from ~/.acp/policy.json, logged to ~/.acp/audit.jsonl. (Non-Bash Codex coverage needs the cloud MCP connector — run without --local.)"},
    {"@type": "HowToStep", "name": "Confirm hook flag flip", "text": "Installer prompts before writing [features].codex_hooks = true to ~/.codex/config.toml. Confirm to enable."},
    {"@type": "HowToStep", "name": "(Optional) Connect a workspace", "text": "Run the installer without --local to open your browser, provision a workspace, add the MCP connector for non-Bash tools, and get team policy plus the cost X-ray. The API key is saved to ~/.acp/credentials."},
    {"@type": "HowToStep", "name": "Restart Codex", "text": "Restart any running Codex sessions for hook + MCP connector to load."}
  ]
}
</script>
