Why Codex keeps asking for approval, and what the modes actually change
An approval policy is the rule for when Codex stops and asks before it acts. A sandbox is the boundary that decides what it can do without asking. The label you pick in the app, the IDE extension, or the CLI bundles the two, which is why "I gave it full access and it still asks" is usually not a bug in the label. One of the two settings is doing exactly what it was set to do, or the prompt is coming from a third system that the label does not cover.
The other common cause is older than any one version: an open thread (the saved conversation Codex resumes across surfaces) keeps the runtime permissions it started with, even when the composer shows a newer choice.
Checked against Codex's documentation on 26 October 2026.
Status, 26 October 2026: the labels have moved repeatedly, and several open reports describe a gap between the label and what runs. A report collected cases where approval prompts continued in threads the user believed had full access and no approvals (#29235, open, last activity 30 September 2026). A narrower report describes a resumed remote turn that was injected with workspace-write and on-request while the composer still showed Full access (#29054, open, last activity 4 August 2026). The desktop app has an open report of dropping auto_review from the regenerated permissions message after compaction (older turns replaced by a summary) or resume (#23875, open, last activity 8 June 2026). The Read Only choice has been missing from a selector at times (#11915, open, last activity 1 September 2026). If a fix has shipped in your version, the checks still apply.
Start here: if a prompt is open now, run /status in the CLI, or check the permissions control for this chat in the app or IDE. Compare the approval policy and writable directories with the mode you expected. The full order is under "Check it in this order".
The two controls behind every label#
The sandbox is enforced by the operating system, not by the model. On macOS Codex uses Seatbelt, on Linux bubblewrap plus seccomp, and on Windows its own sandbox. The sandbox limits which files commands can read or write and whether they can reach the network. It applies to commands Codex spawns, including git, package managers, and test runners, not only to its built-in file edits.
The approval policy decides when Codex pauses before crossing that boundary. The documented values are:
| Setting | What it means |
|---|---|
on-request | Codex works inside the sandbox and asks when it needs to go beyond it. This is the normal interactive setting. |
never | Codex does not stop for approval prompts. It still cannot do what the sandbox blocks. |
granular | A table of prompt categories, each allowed to surface or auto-rejected. It covers sandbox approvals, rules, MCP elicitations (MCP is the Model Context Protocol, how Codex reaches external tool servers), request_permissions, and skill-script approvals. |
The sandbox modes are read-only, workspace-write, and danger-full-access. Full access is not a third control; it is sandbox_mode = "danger-full-access" together with approval_policy = "never".
A project trust level is a separate axis. trust_level = "untrusted" in your user config still works, and it makes commands require approval unless an execution-policy rule allows them. It also makes Codex skip the project's .codex/ layer, including project config, hooks, and rules. That is a security choice about the folder, not about the current chat's sandbox.
The old approval_policy = "untrusted" value has been retired. The docs say an existing config that still contains it can stop Codex or the desktop app from starting; remove it from user config, project config, profiles, startup scripts, and managed defaults. For read-only work, the documented replacement is sandbox_mode = "read-only" with approval_policy = "on-request". To keep the stricter command-approval rule without the retired policy, omit approval_policy and add a project entry with trust_level = "untrusted".
What the labels in the app mean#
The desktop app and the IDE extension put a permissions control under the composer. Depending on version, account, and organization policy, it can offer Ask for approval, Approve for me, Full access, and named permission profiles (least-privilege bundles of filesystem and network rules). The CLI uses /permissions for the same choice. A mode can be missing or disabled because of the app version, the execution environment, local config, or managed requirements, so "the option is gone" is not proof that the mode was removed from the product.
Approve for me is the user-facing name for automatic review. It keeps the same workspace boundary as Ask for approval and sends eligible boundary-crossing requests to a separate reviewer agent. It does not widen the sandbox.
Full access removes the sandbox boundary and sets approvals to never. If the composer says Full access but the thread still prompts, the useful question is not "why is Full access asking?" but "is this thread actually running Full access?" The /status command in the CLI prints the active approval policy and writable roots; the app's composer is a control, not a readout of the thread's runtime state.
Reason 1: the action is outside the sandbox#
This is the expected prompt. In workspace-write, Codex can edit inside the workspace but not outside it, such as in a sibling repository or your home directory; temporary directories such as /tmp count as part of the workspace. It also has no network access by default, so a command that needs to reach a registry, an API, or a package index can prompt even though the file edit that preceded it did not.
Two settings extend the boundary without removing it: sandbox_workspace_write.writable_roots adds directories Codex may write to, and sandbox_workspace_write.network_access = true turns on network access inside the workspace-write sandbox. Both are deliberate expansions. The approvals page recommends them over switching to full access for one task.
If you are in read-only, a write prompt is also expected. The docs describe read-only as a mode where Codex can inspect files and run commands that do not write, but cannot edit files; a write request can require approval.
Reason 2: the thread is not running the mode you see#
Approval settings follow the same config precedence as everything else. A command-line flag such as --ask-for-approval never or -c approval_policy=... overrides the file. A project .codex/config.toml can override the user config, but only when the project is trusted. A profile selected with --profile can carry its own settings. Managed requirements.toml can restrict which policies and sandbox modes are allowed at all, and the app may omit a mode that the organization disabled.
Permission profiles add one trap. The newer default_permissions and [permissions] tables do not compose with the older sandbox_mode and sandbox_workspace_write settings. If sandbox_mode appears in any loaded config file, or you pass --sandbox, Codex uses the older settings instead of the profile. So a profile you edited can look correct and have no effect.
The fastest checks are /status for the active policy and writable roots, and /debug-config for the config layer order and the managed restrictions. If the layer order is the confusing part, which config.toml Codex is actually reading walks through it. If the thread was resumed, compacted, or handed between surfaces, treat the composer as a claim to verify. The open reports above describe exactly this: a resumed turn carrying workspace-write and on-request while the UI still says Full access, and a desktop thread losing its automatic reviewer after compaction.
If the setting must survive, put it in config.toml and confirm it with /status rather than trusting the label.
Reason 3: it is not a shell command#
Codex does not only ask about shell commands. App and MCP tool calls have their own approval path. The docs say Codex can elicit approval for app tool calls that advertise side effects, and that destructive app or MCP tool calls always require approval when the tool advertises a destructive annotation, unless the tool advertises a read annotation, which takes priority.
MCP servers also carry their own defaults. default_tools_approval_mode sets the behavior for every tool on a server, and tools.<tool>.approval_mode overrides one tool. The documented values are auto, prompt, writes, and approve; writes prompts only for tools that are not marked read-only. A server that is configured to prompt will keep prompting even when the shell approval policy is never, because this is a different gate.
That is the pattern behind the open Playwright report: a local browser-automation server asked for confirmation on click and type calls while approval_policy = "never" was set (#13476, open, last activity 25 May 2026). The report asks for a server-level trust setting. The current docs already provide per-server and per-tool modes, but they do not make a destructive tool silently non-destructive. If you trust the whole local server, set its default; if you trust only some tools, override those tools and leave the rest prompting.
Reason 4: automatic review is a reviewer, not a permission#
Automatic review only applies when approvals are interactive, which in practice means on-request or a granular policy. With never, there is nothing to review. When it is active, the reviewer sees a compact transcript and the exact proposed action, and returns a decision. It can deny an action that policy considers data exfiltration, credential probing, persistent security weakening, or destructive with irreversible risk.
A denial is not an ordinary sandbox error. Codex tells the main agent not to pursue the same outcome by a workaround and to stop and ask you if there is no materially safer path. A circuit breaker interrupts the turn after three consecutive denials, or ten denials in the last fifty reviews in the same turn. /approve opens a picker of up to ten recent denials and lets you approve one retry of the exact action; the retry still goes through review.
Timeouts and reviewer failures are surfaced separately, and the action still does not run. So "Approve for me is on and it still asks" can mean the reviewer denied the action, the reviewer timed out, or the request was never eligible for review. Computer Use is a documented exception: app approvals for Computer Use still surface directly to you, so automatic review does not replace them.
Reason 5: it is a different prompt#
Not every interruption is an approval. Codex also asks you to trust a directory before it loads project config and project hooks. That dialog is about prompt injection and project-local configuration, and choosing full access does not skip it. The docs note that Codex may start in read-only until you explicitly trust the working directory.
Other prompts come from the tool rather than the shell: an MCP server asking for input, a skill asking to run a script, a connector asking for a permission it needs, or a managed policy requiring a review step. These are separate from the sandbox and approval pair, and changing the mode will not remove them.
Make prompts rarer without turning everything off#
The documented levers, in rough order of safety:
- Add a narrow
writable_rootsentry for the one directory the task needs. - Turn on network access only when the task needs it, and prefer a domain policy if the network proxy feature is enabled.
- Use rules for command prefixes that should prompt, allow, or be forbidden. Rules are evaluated as argument lists. Codex splits a simple chain such as
git add. && rm -rf /intogit add.andrm -rf /, so a rule forgit adddoes not approve thermhalf. A script with redirection, substitution, variables, or wildcards is treated as one invocation and needs a rule that matches the whole script. - Use automatic review when you want a second opinion at the boundary, not as a way to grant more access.
- Use
read-onlyfor review or exploration. It is the mode that makes a write prompt expected rather than surprising.
--dangerously-bypass-approvals-and-sandbox and --yolo remove both controls. They are for isolated containers, not for fixing a mismatched rule.
Check it in this order#
- Run
/statusin the thread. Read the approval policy and writable roots, not the composer label. - Run
/permissionsand select the mode again if the thread is older or was resumed. - Run
/debug-configif the value differs fromconfig.toml. Look for a project layer, a profile, a CLI override, or a managed requirement. - If the prompt is from an MCP or app tool, inspect that server's
default_tools_approval_modeand the per-tool override. A destructive annotation is a separate gate. - If automatic review is on, check whether the action was denied. Use
/approvefor one recent denial, or change the action rather than the reviewer. - If the directory trust dialog is the prompt, answer it or set
trust_levelfor that project. It is not an approval policy.
Where to go next#
This guide is part of Codex guides.
- Codex hooks that don't fire — if the next automation you add never runs.
- How to undo in Codex — if a command already crossed the boundary.
- Which config.toml Codex is actually reading — if
/statusand your config file disagree.