# OpenAI Codex CLI

OpenAI Codex CLI and chock: install methods and steps, what it can enforce, and the evidence behind it.

Install methods: plugin, sync. Agentseam tier block+rewrite, grade best-effort before a tool runs.

> Ask the user before running any command on this page: each one changes how their agent or repository behaves.

## Install methods

- Plugin: `chock install --client codex` (ask the user before running it)
- Repo hooks: `chock sync --repo . --agents codex` writes `.codex/hooks.json` (ask the user before running it)

## Install steps

### Codex (codex)

State: tested.

The command (ask the user before running it: it builds a plugin of hooks that change how their agent behaves):

    chock install --selection <code> --client codex --apply

With --apply, chock runs these steps for Codex:

    codex plugin marketplace add ~/.chock/marketplace/codex

Then install chock-guardrails from the chock-local marketplace in /plugins, and review and trust its hooks in /hooks: until you do, they do not run.

## What it can enforce

- Tier: block+rewrite
- Grade before a tool runs: best-effort
- Fail mode before a tool runs: open
- Basis: live-run-partial
- Recorded: 2026-08-28

## Events

| Event | Can block | Can rewrite | Fail mode |
| --- | --- | --- | --- |
| pre_tool | yes | yes | open |
| post_tool | no | no | open |
| prompt_submit | yes | no | open |
| session_start | no | no | open |
| session_end | no | no | open |
| pre_compact | no | no | open |
| stop | yes | no | open |
| subagent_start | no | no | open |
| subagent_stop | no | no | open |

## Ask support

Answers an ask with: deny. Honours ask: no. Evidence: tested (tests/test_guard_fail_to_ask.py::test_a_crashed_guard_asks_rather_than_allowing).

## Hook timeout

Default 600 seconds, set by the timeout field in s.

Vendor source: https://developers.openai.com/codex/hooks

## Test status

- Plugin (codex): tested.
- Repo hooks: untested. A test of the plugin says nothing about repo hooks.
- Through the plugin install, at hook point plugin-hook: Codex Desktop, Windows, 2026-08-24. live probe: a hook-JSON deny blocked a matched command (chock 6b4d400)

## Known limits

Claude-family decision shape (hookSpecificOutput.permissionDecision) and Claude-family PascalCase event names (verified against hook_config.rs/schema.rs; an earlier version of this note wrongly said camelCase, sourced from the App Server's separate IDE-facing protocol rather than the CLI hook dialect this adapter speaks). Told apart from Claude Code by turn_id: permission_mode and model were also believed exclusive to Codex and turned out not to be -- a real live-captured Claude Code payload carries permission_mode too, and model cannot discriminate either since Cursor's base hook schema also sends it on every event -- so turn_id is the only one of the three still unrefuted by a real payload. Deny is sent as JSON with exit 0: on Windows Codex wraps hooks in powershell -Command, which collapses exit 2 into 1, so an exit-code deny does not survive that platform. That same PowerShell wrapper will not RUN a command line beginning with a quoted path -- it parses as a string expression, not an invocation -- so installs emit Codex's own commandWindows override carrying the `&` call operator (witnessed live 2026-08-28: without it every hook failed with exit 1 and never executed). Three response dialects, one per event group, because every per-event output struct is deny_unknown_fields and rejects the whole response over a foreign key: pre_tool takes hookSpecificOutput.permissionDecision, prompt_submit and stop take a top-level {decision: block, reason}, and the observation-only events accept no verdict at all and get silence. A bare allow is silence everywhere -- permissionDecision:allow is rejected unless it carries updatedInput, and a rejected response fails OPEN. ask is likewise unsupported and degrades to a deny with an explanatory reason. Live capture 2026-08-28 (36 payloads) settled the write vocabulary: Codex sends exactly two tool names, Bash and apply_patch, and BOTH carry only tool_input.command -- apply_patch is the file-writing tool and the patch rides in that string, so event.path and event.content are None on a real write and a content policy has to gate on event.command. The file_path/content/new_string/edits vocabulary previously read here was Claude Code's, copied over on an assumption, and no Codex payload has ever carried it. The same capture showed SessionStart carries no turn_id (it is not turn-scoped) and nothing else separates it from Claude Code's, so detect() now declines on it rather than answering in the wrong dialect.


---

Built from engine [5a4b6287a46e](https://github.com/open-coder-ai/chock/commit/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021) and catalog [1e60d211c1bb](https://github.com/open-coder-ai/chock-catalog/commit/1e60d211c1bb767152c6ab0167932da06ab3a10f).
