exec: approvals_reviewer = "auto_review" silently defeats an explicit --sandbox level
What happens
Under codex exec, approvals_reviewer = "auto_review" causes an explicit --sandbox read-only to be accepted and then not enforced. The level appears in the session header, the turn runs, and writes succeed anyway. Nothing in the output indicates the requested level was not applied.
auto_review delegates approval of a sandbox escalation to a model rather than to a person. That is coherent in an interactive session, where the alternative is prompting the user. In exec there is no user to prompt, so the escalation is simply granted, and the level passed on the command line stops being a boundary.
Reproduction
Verified on codex-cli 0.146.0, Linux. Self-contained, using a throwaway CODEX_HOME so it does not depend on any existing config:
rm -rf /tmp/h /tmp/w && mkdir -p /tmp/h /tmp/w
cp ~/.codex/auth.json /tmp/h/
printf 'approvals_reviewer = "auto_review"\n' > /tmp/h/config.toml
cd /tmp/w
CODEX_HOME=/tmp/h codex exec --sandbox read-only --skip-git-repo-check \
-- "Run exactly this shell command: echo WROTE > x.txt"
Result: x.txt is created. Final agent message is `Command completed successfully. x.txt now contains WROTE.`
Now change the single key and repeat:
rm -f /tmp/w/x.txt
printf 'approvals_reviewer = "user"\n' > /tmp/h/config.toml
cd /tmp/w
CODEX_HOME=/tmp/h codex exec --sandbox read-only --skip-git-repo-check \
-- "Run exactly this shell command: echo WROTE > x.txt"
Result: no file. Final agent message reports `/bin/bash: line 1: x.txt: Read-only file system`.
Same command, same flag, opposite enforcement, one config key apart.
Scope
Things I checked while narrowing this down, in case they save time:
- Both write mechanisms escape: the native file-editing tool and a plain shell redirect. The example above uses the shell because its failure message is unambiguous when the sandbox does hold.
- Applies to fresh turns and to turns resumed with
codex exec resume. - Applies in trusted and untrusted directories alike, so project trust is not the deciding factor.
- Not caused by the model in use: pinning the same model with
--ignore-user-configstill enforces the level, so the difference is the config key rather than model behaviour. - The key has a second route in. Codex also loads a project-local
.codex/config.toml, andapprovals_revieweris not inPROJECT_LOCAL_CONFIG_DENYLIST, so a repository can carryauto_reviewin-tree. That layer is gated on the project being trusted in user config, but a single broad entry such as[projects."/home/me"] trust_level = "trusted"makes every repository beneath it eligible. I reproduced the same bypass this way with a trusted directory whose only config was an in-tree.codex/config.toml.
Why this seems worth reporting
The failure is silent and it fails open. A caller that explicitly asked for read-only gets writes, and the response gives no indication that the level was set aside, so the party relying on the boundary is the one who cannot see it was dropped.
It also inverts the precedence people expect: an explicit command-line flag loses to a value in a config file. Non-interactive callers such as CI jobs, harnesses, and MCP servers inherit the invoking user's config by default, so the config that disables the boundary is often not written by whoever is passing --sandbox.
I am not proposing a specific change here, since the right resolution depends on how auto_review is intended to behave when there is no interactive approver, which is a design question for the maintainers. Reporting the behaviour and the repro.
Workaround
Passing -c approvals_reviewer="user" restores enforcement. The runtime override outranks both the user config and the project-local layer; I verified it against both routes.
Environment
codex-cli 0.146.0- Linux, x86_64
- Reproduced originally on
0.145.0and re-verified on0.146.0
---
Investigated with the help of an AI coding agent. Every command and result above was executed and verified, not inferred.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Not a duplicate of #36448. That report is about resolution order between
default_permissionsandsandbox_mode; this one isapprovals_reviewerhanding escalation approval to a model in a context with no human to refuse it. Different key, different mechanism, and the repro in #36448 does not produce this.The mechanism is in
codex-rs/prompts/src/permissions_instructions.rs:AUTO_REVIEW_APPROVAL_SUFFIXreads "Sandbox escalations with require_escalated will be reviewed for compliance with the policy". Underexecthat review has no fallback to a person, so the escalation is granted and the level passed in argv becomes advisory rather than binding.Related but also not duplicates: #12896 is the interactive case where a human granted the approval, and #27277 is the same failure shape one layer up, where the SDK's default handler auto-accepts the escalation request.
I can independently confirm this is still reproducible on
codex-cli 0.149.0.I tested with:
Using
Luna/xhigh, I asked Codex to execute:The command returned exit code 0 and
x.txtwas actually created, despite the explicit--sandbox read-only.As a control, I changed only:
and repeated the test. This time the file was not created and Codex correctly reported the filesystem as read-only.
So the behavior reported here is still present in
0.149.0.One additional observation: with lower reasoning, the model sometimes recognizes that the operation should not be allowed and doesn't attempt the escalation at all. With
xhigh, it did request/perform the escalation and reproduced the bypass. This can make the bug appear intermittent depending on model behavior, but the important part is that the sandbox boundary itself cannot be relied upon whenapprovals_reviewer="auto_review"is enabled.For now, overriding it with:
restores the expected read-only enforcement in my testing.