Skip to content

Course · Codex · Approvals & sandbox

The approval policies - on-request, never, and the granular form

Codex splits trust into two dials. The sandbox (-s, the wall) sets where the agent may work - three levels, read-only, workspace-write, and danger-full-access, covered in the sandbox lesson; this chapter’s model lesson shows why they are separate dials. The approval policy (-a, this lesson) decides which of the actions the sandbox permits still stop to ask you first. Picture the wall with checkpoints at the gates in it: the sandbox draws the wall, the approval policy staffs the gates. Two policies, plus one config-only form for finer control.

A policy that used to exist, and doesn’t

Section titled “A policy that used to exist, and doesn’t”

Older Codex material describes a third policy, untrusted: pause for anything not on a known-safe list of commands. It has been retired - the migration guidance in the agent approvals and security docs says the CLI no longer supports approval_policy = "untrusted" and that a config still carrying it can prevent Codex from starting, so remove it wherever it lingers (checked October 2026). The known-safe list went with it. For the tight, watch-everything posture it used to provide, today’s equivalents are read-only + on-request - every edit and every writing command stops - or, where fail-shut is the point, never.

on-request - work inside, ask at the boundary

Section titled “on-request - work inside, ask at the boundary”
codex -a on-request

The policy’s logic is a boundary, not a judgment call. Under on-request, Codex works inside the active sandbox without asking - reads, edits, and routine commands wherever the sandbox permits them - and pauses for your y/n when an action needs to cross that boundary: a write outside the workspace, a network call while network access is off. That is how the agent approvals and security docs define the policy, checked October 2026. With a whole policy set, the stops are the fence crossings, and you can count them from the shape of the job (the granular form below can add or remove stops per category; the precise trigger semantics live in the config reference).

Take a typical in-fence job - four files read, two edited, one test run, all inside the workspace - and the count falls straight out: zero stops, because nothing the job does crosses the boundary. It isn’t that the agent judged the edits safe; the question never came up. Hand it a job whose first move is an HTTP call - fetching a live exchange rate, say - and it stops once, at the network line, because that is a crossing.

This is the daily driver, and the right answer for most interactive work. You keep a checkpoint on every escalation - the moves that would leave the project or open a socket - and pay zero interruptions on everything inside. You’re not trusting the agent’s sense of risk; you’re placing a gate on a line the sandbox draws. The agent cannot redraft the boundary; it can only ask to cross it. This is the control principle at its most literal: not guidance the agent may weigh, but a checkpoint it has to pass.

codex -a never

The agent never pauses for approval. It runs the whole task start to finish, and you review the result afterward rather than the steps along the way.

This sounds reckless, and on a one-dial tool it would be - here’s where the independence between the two axes earns its keep. never only removes the checkpoint. It does nothing to the wall. Under workspace-write + never, an in-fence job runs unattended with zero stops - and if some step had needed the network, it would simply fail, not prompt. Unattended work fails shut at the fence.

The pairing decides everything. -a never -s read-only runs autonomously with read-only reach: it can inspect the whole tree, reason, and answer in the conversation, and it cannot mutate a file - the right shape for “audit this code and summarize what you find.” -a never -s danger-full-access removes checkpoint and wall at once; it has its own dedicated flag, which we’ll meet - and warn you off - in the profiles lesson. Zero stops is only safe when the wall makes zero stops harmless.

Typing -a every invocation gets old. The persistent equivalent is approval_policy in config.toml. It takes on-request, never, or the granular table:

# ~/.codex/config.toml or .codex/config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"

That config.toml pairing is the same two dials, written down once instead of typed every time. You’ll see in the last lesson how to give a named pairing - a profile you can summon with one flag.

The granular form - when a whole policy is too coarse

Section titled “The granular form - when a whole policy is too coarse”

Both whole policies treat every crossing the same way: on-request asks about all of them, never asks about none. When that is too coarse - you want one category of prompt to stop for you and another to flow - the third form shapes approval per category:

# illustrative shape; the config reference documents each field's values
approval_policy = { granular = { ... } }

The table’s fields each name a prompt category: sandbox_approval (requests to cross the sandbox boundary), mcp_elicitations (confirmations raised by MCP servers), and rules, skill_approval, and request_permissions, which gate their own respective prompt categories as the reference defines them. The decision rule for reaching for it: only when a whole policy demonstrably interrupts you on a category you have decided to trust - not preemptively, and not before you have felt the friction. The field-by-field values and defaults live in the config reference; treat that page as the source of truth, checked October 2026.

Newer controls - an automatic reviewer and permission profiles

Section titled “Newer controls - an automatic reviewer and permission profiles”

Three controls arrived after the two-policy model above. The first two change who answers a prompt; the third replaces how the sandbox is described. Evidence label for this section: published. The configuration keys and behavior below come from the auto-review and permissions docs and the agent approvals and security docs, read October 2026. None of it was run for this course. The flag’s help text was read from a local codex-cli 0.163.0 with codex --help; no session was started.

An automatic reviewer (approvals_reviewer). By default approvals_reviewer = "user", so every approval request comes to you. Set approvals_reviewer = "auto_review" alongside an interactive policy such as approval_policy = "on-request" and a separate reviewer agent answers the prompts instead. The docs name the approval policy alone as not selecting the reviewer.

codex --sandbox workspace-write --ask-for-approval on-request -c approvals_reviewer=auto_review

That command is the example form in the agent approvals docs. Four limits from the docs decide whether it fits your job:

  • It reviews only requests that would already stop for you: sandbox escalations, blocked network requests, edits outside writable roots, and MCP or app tool calls that need approval. Actions inside the sandbox still run without review.
  • It swaps the reviewer and grants nothing. It does not widen writable roots, open the network, or relax protected paths.
  • With approval_policy = "never" there is nothing to review, and full access can skip the requests review depends on.
  • Reviews use extra model calls, which can add to Codex usage. After 3 consecutive denials, or 10 in the last 50 reviews, the turn is interrupted.

--approve-for-me. codex --help on 0.163.0 lists it as “Route approval requests through automatic review using the workspace-write sandbox”, which reads as a shortcut for the combination above. The docs pages checked do not mention the flag, so treat that reading as unconfirmed: the help text states what the flag does, not which config keys it sets.

Permission profiles (beta). default_permissions names the active profile: a built-in (:read-only, :workspace, :danger-full-access) or your own [permissions.<name>] table, which can start from another profile with extends and then set filesystem and network rules. Two cautions from the docs:

  • They are a different mechanism from the layer-file profiles in the profiles lesson, and the two share only a word. A layer file is selected with --profile; a permission profile is selected with default_permissions.
  • Permission profiles do not compose with sandbox_mode. If sandbox_mode appears in any loaded config, is passed with --sandbox, or comes from the selected --profile layer, Codex uses the older settings and ignores default_permissions.

The -s sandbox levels taught in this chapter therefore remain the working model for the examples here. Domain rules under network.domains also need features.network_proxy = true to be enforced. The field-level syntax is on the permissions page, which is the source of truth.

One more boundary matters before profiles: outbound network access is off by default in the sandbox, so a task whose first move needs the network - a live exchange rate, a package registry, an API - fails until you open it on purpose: why network is off, and how to widen the fence deliberately.