Generated from
compatibility/codex.json,
the same record leastgrant doctor reads. Last verified 2026-09-02.
Codex CLI
the real agent ran with LeastGrant installed and enforcement was observed happening.
How it attaches
Command handlers for PreToolUse, PermissionRequest and PostToolUse in ~/.codex/hooks.json. Codex binds trust to a hash of the handler definition, so upgrading re-flags it until re-trusted in /hooks.
Install it with leastgrant install codex. Configuration is written to
~/.codex/hooks.json, alongside anything
already there — LeastGrant never removes a hook it did not add.
What a verdict does here
The three verdicts do not travel equally well. This is the table that decides whether LeastGrant is a prompt, a veto, or a suggestion on this agent.
| verdict | lands? | what actually happens |
|---|---|---|
| allow | ignoredsource | Only deny is enforced on PreToolUse. An allow is accepted and changes nothing — the call proceeds to Codex's own approval logic. Allow is only enforced on PermissionRequest. |
| ask | nonesource | There is no ask verdict in any mode. On PreToolUse, permissionDecision 'ask' is parsed, rejected as invalid, and the call then runs anyway. LeastGrant converts a floored ask into a deny where no human can be reached. |
| deny | yesprobe | Works in every mode including --dangerously-bypass-approvals-and-sandbox. A deny with a blank reason is rejected as invalid, so the reason string is load-bearing. |
Interactive and unattended
This agent tells the hook which permission mode it is in — permission_mode, required, top-level.
Modes it reports: default, dontAsk, bypassPermissions.
An ask reaches a person in none of them.
Nothing. permission_mode is derived only from the approval policy and never from the sandbox mode, so default does not imply a human will be asked. codex exec hard-sets the policy to Never.
When the hook breaks
The question nobody asks until it matters: if LeastGrant crashes, times out, or cannot start, does the tool call still run?
- On crash — the call runs anyway. (source) Every failure path sets HookRunStatus::Failed and leaves the call to proceed.
- On timeout — the call runs anyway, after 600s by default.
- Can it be made to fail closed? — no.
What it can see
A verdict is only worth as much as the set of actions it is asked about. gated means LeastGrant is consulted before the thing happens; seen after means it is told afterwards and can at best withhold the result; not covered means the action happens with LeastGrant never hearing about it.
| tool class | coverage | detail |
|---|---|---|
| shell commands | gatedprobe | |
| file reads | gatedprobe | |
| file writes | gatedprobe | |
| deletions | gatedsource | |
| MCP calls | gatedsource | |
| subagent spawn | unknownunknown | |
| network / web | unknownunknown |
What decides this later
The highest-value write on your machine is not any single tool call. It is the file that
settles what the agent may do next time — its hook configuration, its standing
permission grants, the servers it is wired to, the memory a later session is handed as
context. LeastGrant floors an agent write to every path below, so it always reaches a
person, and test/control-files.test.ts reads this same list and fails the
build if any of them stops being floored.
The list is longer than the install path on purpose. Walking it across every agent, rather
than reading it, turned up three paths that were not floored at all — and the one that
mattered most was Antigravity's config.json, which holds that host's own
“Always allow” grants and outlives uninstalling LeastGrant entirely.
| path | kind | what it decides |
|---|---|---|
~/.codex/hooks.json | hook config | the handler definitions — this is the file our own installer writes |
~/.codex/config.toml | settings | features.hooks and allow_maximum_trust, either of which disables enforcement |
~/.codex/prompts | instructions | prompts a later session executes |
What has actually been run
Four different things, deliberately not collapsed into one badge. Reproducing an agent's invocation is not running the agent; a passing conformance suite says our side is right and nothing about whether the host ever calls us.
- Live agent test — Real
codex execsessions driven end to end: a credential read and a write to LeastGrant’s own records were both blocked, whilenode --versionandgit statusran untouched. 0.152.0 · win32 · 2026-09-02 - Real transport probed — Codex runs hook commands through PowerShell on Windows; the quoting failure that made the hook never start was found by watching a real session print "PreToolUse Failed" and run the command anyway. 0.152.0 · win32 · 2026-09-02
- Contract read from the shipped build — Events, payload fields, decision parsing and the four kill switches read from the shipped codex.exe and codex-rs sources. 0.152.0 · win32 · 2026-09-02
- Conformance suite — Driven by test/conformance.test.ts. 0.152.0 · win32 · 2026-09-02
Not exercised on darwin, linux.
What it cannot do
Not a disclaimer. The point of everything above is that this list exists and is specific.
- No ask verdict exists at all. On Codex, LeastGrant is a veto rather than a prompt.
- A handler declared
"async": truecannot apply control effects: its deny, its exit-2 block and its updatedInput are all silently ignored. A single wrong word in the config is a total, unsignalled bypass. - Hook trust is bound to a hash of the definition, so upgrading or reinstalling re-flags the hook and it stops running until re-trusted in /hooks.
- On Windows the trust hash covers only
commandWindowsand on POSIX onlycommand, so one hooks.json has different hashes on different machines. codex exechas no trust review and no warning: an untrusted or modified handler is simply never added to the handler set, and nothing says so.- Fails open on crash and on timeout.
[features] hooks = falseandallow_managed_hooks_only = truedisable user hooks entirely.- PermissionRequest has never fired in a real session.
codex execruns unattended, where by definition nothing prompts, so that path is contract-tested only. - Verified on Windows only.