Guide
Codex CLI sandbox modes: read-only, workspace-write, and when each is right
Codex CLI has a feature almost nothing else in the terminal-agent category has: a sandbox the
operating system enforces. Not a system prompt asking the model to be careful, not a regex over
proposed commands — an Apple Seatbelt profile on macOS and a bubblewrap container on Linux, so a
write the policy forbids fails with EPERM whether or not the model intended it.
It is also one of the worst-explained features in the category. The flag names have changed, one of them was removed, the mode you get by default depends on a decision you made weeks ago in a different folder, and half the internet conflates the sandbox with the approval prompt. This is the guide.
Verified against: @openai/codex 0.146.0 (npm
latest, tagged rust-v0.146.0, released 2026-07-29), checked on
2026-08-02 against the source in
github.com/openai/codex and the
docs at developers.openai.com/codex (which now redirect to
learn.chatgpt.com/docs). Everything below names the file it came from. This part of
Codex moves fast — re-check flag names against codex --help before you paste
anything into CI.
Two dials, not one
The single most common mistake is treating "sandbox" and "approvals" as one setting. They are two, and they answer different questions:
- Sandbox mode — what a command is physically capable of, once it is running. Enforced by the kernel. The model cannot argue with it.
- Approval policy — what happens when the agent wants to do something the sandbox would block. Ask you, or refuse and feed the failure back to the model.
The three modes
The values live in SandboxMode in codex-rs/protocol/src/config_types.rs,
and the CLI mirrors them one-to-one in codex-rs/utils/cli/src/sandbox_mode_cli_arg.rs:
| Mode | Filesystem writes | Network | Running processes |
|---|---|---|---|
read-only | None. Every write path fails at the syscall. | Off by default. | Allowed. The agent can run ls, rg, git log, a test runner — anything that does not write. |
workspace-write | Writable roots only: your cwd, plus TMPDIR and /tmp on Unix, plus anything in writable_roots. | Off by default. network_access = true turns it on. | Allowed. |
danger-full-access | Everything. No sandbox is applied. | On. | Allowed. |
Two details worth pinning down, because the prose docs blur them.
Read-only does not mean "cannot run commands." The Seatbelt profile Codex ships
(codex-rs/sandboxing/src/seatbelt_base_policy.sbpl) opens with
(deny default) and then explicitly grants (allow process-exec) and
(allow process-fork), with the note that "child processes inherit the policy of their
parent." A read-only Codex is a Codex that can explore your repo at full speed and cannot leave a
mark on it. That distinction is the whole point of the mode.
Workspace-write is not "the whole workspace." Under a writable root, Codex keeps
three metadata directories protected: .git, .agents and
.codex (PROTECTED_METADATA_PATH_NAMES in
codex-rs/protocol/src/permissions.rs). The reason is escalation, not tidiness —
a writable .git/hooks is a writable arbitrary-code-execution path. On Linux those
subpaths are re-applied as read-only bind mounts after the workspace is bound writable.
How the enforcement actually works, per OS
Codex picks a backend by host OS in get_platform_sandbox()
(codex-rs/sandboxing/src/manager.rs), and the three arms are not equivalent.
macOS — Apple Seatbelt
Each sandboxed command is wrapped in a Seatbelt profile (the crate calls it
SandboxType::MacosSeatbelt). The base policy is closed-by-default and modelled on
Chrome's renderer sandbox — the file cites Chromium's common.sb as its
inspiration. Reads of platform paths, PTYs, a small allowlist of sysctl names and
/dev/null writes are granted back; everything else is denied unless the policy adds
it. Network is a separate overlay profile
(seatbelt_network_policy.sbpl) that is only concatenated when network access is
enabled. This works out of the box on any Mac — nothing to install.
Linux — bubblewrap plus seccomp (Landlock is now the legacy path)
This is the part most write-ups get wrong, including ones published this year. Codex used to
enforce the Linux sandbox with Landlock LSM rules plus a seccomp filter. As of the current
release, bubblewrap is the default filesystem sandbox and Landlock is an opt-in
fallback. From codex-rs/linux-sandbox/README.md:
- The whole filesystem is bound read-only with
--ro-bind / /; writable roots are layered back in with--bind <root> <root>. - Protected subpaths under a writable root (
.git, a resolvedgitdir:,.codex) are re-applied as read-only with--ro-bind. - The helper unshares the user namespace (
--unshare-user) and the PID namespace (--unshare-pid), and appliesPR_SET_NO_NEW_PRIVSplus an in-process seccomp network filter. - When network is restricted and no proxy is configured, it also unshares the network namespace (
--unshare-net). That is why a blocked network is a blocked network and not a hopeful environment variable. - Codex prefers a
bwrapfound onPATH, falls back to a bundledcodex-resources/bwrap, and prints a startup warning when it has to. - To force the old behaviour:
features.use_legacy_landlock = true, or-c use_legacy_landlock=trueon the command line.
The practical consequences: WSL2 works, WSL1 does not (it cannot create the required user namespaces, and Codex rejects sandboxed shell commands rather than running them unprotected), and a hardened container that forbids user namespaces will trip the same warning. If you run Codex inside Docker, check that before you assume the sandbox is on.
Windows — off unless you turn it on
Windows now has a native backend, SandboxType::WindowsRestrictedToken, but
WindowsSandboxLevel defaults to Disabled and is gated behind a feature
flag or an explicit config key (codex-rs/core/src/windows_sandbox.rs resolves it from
the [windows] table, sandbox = "unelevated" or "elevated").
The behaviour when it is off is the single most important platform difference in this post, and it
is in codex-rs/config/src/config_toml.rs: on Windows with the sandbox disabled, a
resolved mode of workspace-write is silently downgraded to
read-only. Codex would rather be uselessly safe than pretend. If you are on
Windows and wondering why the agent keeps refusing to write, this is why — and the honest
fix is WSL2 or the opt-in native sandbox, not danger-full-access.
The second dial: approval policy
AskForApproval (codex-rs/protocol/src/protocol.rs) has these values:
| Value | What it does |
|---|---|
untrusted | Only "known safe" read-only commands auto-run. Everything else asks you. |
on-request | The default. The agent works inside the sandbox and asks when it wants to step outside. |
never | No prompts, ever. A blocked action returns its failure straight to the model. |
granular | A struct, not a CLI value: per-category booleans (sandbox_approval, rules, skill_approval) where false auto-rejects instead of prompting. |
Two things here contradict what you will read elsewhere. on-failure is
no longer a distinct policy — it survives only as a serde alias that deserialises
to on-request, so an old approval_policy = "on-failure" in your
config.toml loads without complaint and silently means something else. And
granular is not selectable from the command line; the
--ask-for-approval enum exposes exactly three values.
The combination is what decides your day. From codex-rs/core/src/safety.rs, for a
patch that wants to write outside the writable roots:
| Sandbox | Approval | Result when the agent tries to write |
|---|---|---|
read-only | never | Hard refusal: "writing is blocked by read-only sandbox; rejected by user approval settings". The model reads that and adapts. |
read-only | on-request | You get a prompt. Say yes and the write happens outside the sandbox. |
workspace-write | never | Writes inside the roots go through silently. Writes outside are refused. |
workspace-write | untrusted | Every non-trivial command asks first, even the ones the sandbox would have allowed. |
danger-full-access | never | Nothing stops anything. Only defensible when something else is the sandbox. |
Note the asymmetry: untrusted makes an already-safe sandbox noisier, not
safer. If you want quiet safety, the answer is a tighter sandbox with never, not a
louder approval policy.
The default is not a constant
If you set nothing, what do you get? The resolution logic in
ConfigToml::derive_permission_profile is, in its own comment: "If no
sandbox_mode is set but this directory has a trust decision, default to
workspace-write except on unsandboxed Windows where we default to read-only." Otherwise it falls
through to SandboxMode::default(), which is read-only.
In plain terms: a folder you have already answered the trust prompt for gets
workspace-write; a folder you have not gets read-only. Two clones of the
same repo in two paths can behave differently. This is fine interactively and unacceptable in a
script. Always pass --sandbox explicitly in anything automated.
codex exec versus the TUI
codex exec is the non-interactive entry point: it takes a prompt as an argument or on
stdin, streams progress to stderr, and puts the final answer on stdout. The permission surface is
deliberately narrower than the TUI's, and the difference is not cosmetic.
-
-a/--ask-for-approvaldoes not exist oncodex exec. It is declared on the interactivecodexcommand only (codex-rs/tui/src/cli.rs);codex exechardcodesapproval_policy: Some(AskForApproval::Never)incodex-rs/exec/src/lib.rs, with the comment "Default to never ask for approvals in headless mode." There is no human at the end of a pipe, so a prompt would be a hang. -
--sandbox/-sis shared between both, along with--cd/-C,--add-dir, and--dangerously-bypass-approvals-and-sandbox(alias--yolo). -
--full-autois gone. It no longer parses on the top-levelcodexcommand at all; oncodex execit is a hidden compatibility trap that prints "warning:--full-autois deprecated; use--sandbox workspace-writeinstead." Any tutorial still recommending it is out of date.
Which gives the combination you want whenever a script, a CI job, or another tool is driving
Codex: read-only plus non-interactive. The sandbox guarantees the run cannot
mutate anything; never guarantees it cannot stall waiting for a keystroke nobody will
press. Failure becomes a non-zero exit and a message, which is what automation can actually
handle.
Four recipes
1 — Review a diff without letting the agent touch anything
codex exec --sandbox read-only \
"Review the uncommitted changes. Flag correctness bugs and missing tests. Do not suggest style nits."
Codex ships a purpose-built subcommand for this too, and it accepts the same
--sandbox:
codex exec --sandbox read-only review --base main
codex exec --sandbox read-only review --uncommitted
codex exec --sandbox read-only review --commit a1b2c3d
The agent can run git diff, open every file the diff touches, grep for callers and
run a read-only test listing. It cannot "helpfully" apply the fix it just described. For a review
pass that is a feature, not a limitation — a reviewer that edits is no longer a reviewer.
2 — Let it write in the workspace, but never reach the network
This is the everyday local setting, and it is already the default for workspace-write
— network_access is false unless you say otherwise. Make it
explicit in ~/.codex/config.toml:
sandbox_mode = "workspace-write"
approval_policy = "on-request"
[sandbox_workspace_write]
network_access = false
writable_roots = ["/Users/you/scratch"]
exclude_tmpdir_env_var = false
exclude_slash_tmp = false
An agent that can edit your repo but cannot open a socket cannot exfiltrate anything it reads, and
cannot install a package because a stale error message suggested it. The cost is real: no
npm install, no pip install, no git fetch. Codex sets
CODEX_SANDBOX_NETWORK_DISABLED in the child environment when network is restricted
(and CODEX_SANDBOX=seatbelt on macOS), so your test suite can branch on it and skip
the network-dependent cases instead of failing them.
If you need to widen the write boundary for one run rather than forever, --add-dir
takes an extra writable directory without editing config.
3 — Running it in CI
CODEX_API_KEY="$OPENAI_API_KEY" codex exec \
--sandbox read-only \
--skip-git-repo-check \
--json \
-o /tmp/codex-verdict.md \
"Summarise the risk in this PR. Reply with the single word OK if there is none."
The pieces: --json emits JSON Lines so a downstream step can parse events rather than
scrape prose; -o / --output-last-message writes just the final message
to a file; --skip-git-repo-check matters because Codex otherwise insists on being in
a repo, and some runners check out into odd shapes. --ephemeral keeps session files
off the runner's disk entirely.
Do not reach for --dangerously-bypass-approvals-and-sandbox as the CI default. Its
own help text says it is "intended solely for running in environments that are externally
sandboxed" — if your job already runs in a throwaway container with no credentials worth
stealing, that is a defensible use. A self-hosted runner with a cached npm token is not.
4 — Check what the sandbox actually does before you trust it
codex sandbox -- touch /tmp/probe
codex sandbox -- curl -sS https://example.com codex sandbox ("Run commands within a Codex-provided sandbox") runs an arbitrary
command under the same backend Codex would use, choosing Seatbelt, the Linux helper or the Windows
restricted token by host OS. On macOS, --log-denials streams the sandbox denials that
the command triggered and prints them after exit — the fastest way to find out which path
your build tool wanted that the policy said no to. -P /
--permission-profile selects a named profile from your config so you can probe the
exact policy a session will run under.
Why a read-only agent is worth more than it sounds
We build on this at Backgrind, so this is direct experience rather than speculation. A read-only
Codex is exactly what makes it safe to hand the same hard question to several agents at once and
compare what comes back. Each one can explore the real repository — read the actual code, run
git log, trace the actual call graph — and none of them can change it while doing so.
You get four grounded opinions and one unchanged working tree.
Without an OS-enforced read-only mode this is not a thing you can do. Two agents editing the same files concurrently produce a mess neither of them can explain, so the alternative is either running them one at a time or giving each its own worktree and reconciling afterwards. Read-only removes the conflict entirely: exploring a repo is the one operation that parallelises for free. That is the mechanism behind our Fusion council, which convenes Codex alongside Claude Code, Cursor and Gemini — every seat headless and read-only in your repo, one chairman folding the answers into a single verdict.
What to actually set
- Scripts and CI:
codex exec --sandbox read-only. Non-negotiable unless the job's whole purpose is to produce a diff. - Everyday local work:
workspace-writewithnetwork_access = falseandon-request. Prompts stay rare because the sandbox already covers the common case. - A repo you do not know yet:
read-onlyplusnever, and read what it says before you loosen anything. - Anything you would call "full auto": reach for
--sandbox workspace-write, not the removed--full-autoand notdanger-full-access. - On Windows: assume nothing is enforced until you have turned on the native sandbox or moved to WSL2. The silent downgrade to read-only is your evidence either way.
New to Codex? Start with setting up the CLI. Weighing it against the alternative? Claude Code vs Codex CLI covers the rest of the comparison — the sandbox is the strongest single argument on Codex's side of it.
Frequently asked questions
What are the Codex CLI sandbox modes?
Three: read-only, workspace-write and danger-full-access, selected with --sandbox / -s or sandbox_mode in ~/.codex/config.toml. Read-only still lets the agent run processes; it just cannot write, and has no network by default.
Is the sandbox enforced by the OS or just by the model?
By the OS on macOS and Linux — an Apple Seatbelt profile that starts from (deny default), or a bubblewrap helper that binds / read-only and adds a seccomp network filter. On Windows the native restricted-token sandbox is off by default, and Codex downgrades workspace-write to read-only rather than pretend.
Sandbox mode vs approval policy — what is the difference?
Sandbox mode is what a command can physically do. Approval policy is what happens when the agent wants more: prompt you (untrusted, on-request) or refuse and return the failure to the model (never). Independent dials; the combination is what you feel.
Does codex exec have --ask-for-approval?
No. As of 0.146.0 that flag is defined only on the interactive codex command. codex exec sets the policy to never for you. Your only permission dial in exec is --sandbox.
What is the default sandbox mode?
It varies by folder: read-only where Codex has no recorded trust decision, workspace-write where it does. codex exec is documented as read-only by default. Pass --sandbox explicitly in anything automated.
Whatever happened to --full-auto?
Removed. It no longer parses on the top-level codex command, and on codex exec it survives only as a hidden flag that warns you to use --sandbox workspace-write instead.