Skip to content

Course · Codex · Approvals & sandbox

Profiles - bundle an approval × sandbox pairing and summon it with one flag

Setting both dials by hand looks like this: on-request with workspace-write for ordinary work (-a is the approval policy, which decides when work stops to ask you; -s is the sandbox, which decides how far the agent can reach), the same plus network_access in config when the task needs the internet, --add-dir ~/budget-data when it needs a data directory outside the repo. If you’re typing that pairing every launch - or editing config.toml for one task that the next task shouldn’t inherit - you’re paying twice for the same decision.

Both are the same friction: a trust posture you keep re-deriving (a posture being the complete set of trust decisions for a kind of work). A profile fixes it. A profile is a whole posture - approval policy, sandbox, and the workspace-write details - written down once under a name, and pulled in with one flag.

The persistent keys are approval_policy and sandbox_mode at the top level of config.toml, plus the [sandbox_workspace_write] block for network and writable roots. A profile is those same keys, written at the top level of a layer file of their own sitting next to your base config at ~/.codex/config.toml - ~/.codex/<name>.config.toml (a genuine layer file, not a [profiles.name] table inside config.toml; the config reference documents the layout, checked October 2026). The layering is what fixes the leakage the opening described: while a profile is active, any key it sets replaces the base file’s value, and any key it omits falls back to the base. A config.toml still carrying last week’s approval_policy = "never" experiment loses to budget-build’s on-request for exactly the keys the profile names - and nothing else changes. One file per posture is what lets you keep several side by side and choose between them per launch:

~/.codex/budget-build.config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
writable_roots = ["~/budget-data"]

That file is an entire posture as one thing called budget-build: edit freely inside the workspace, stop at the boundary, reach the API the task needs, write the one extra data directory - and nothing else. Summon it with --profile (short form -p):

codex --profile budget-build
codex -p budget-build # same thing

No -a, no -s, no --add-dir - the name carries all of it. The values are exactly the ones you’d otherwise type, so a profile changes nothing about the underlying mechanics: approval policy and sandbox are still two independent dials - on-request (ask at the fence) or never (never pause) crossed with read-only, workspace-write, or danger-full-access (no fence) - and a profile just records one cell of that grid so you stop re-deriving the pairing every morning. Any cell you can name, a profile can hold.

The reason profiles beat a single config.toml is that real work isn’t one posture. Auditing code for problems wants full autonomy and zero reach; building wants the comfortable daily cell. Write one file each, switch with the flag:

~/.codex/budget-audit.config.toml
approval_policy = "never"
sandbox_mode = "read-only"
~/.codex/budget-build.config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
writable_roots = ["~/budget-data"]
codex -p budget-audit # runs end to end, can't change a byte
codex -p budget-build # the daily driver, fenced to workspace + data dir

budget-audit is the safe-autonomy corner of that grid - never × read-only, full speed, no write risk because nothing can write; the agent can still read anything your account can, so point it only where reading is fine. budget-build is the cell you live in. Two names, two reflexes; you stop thinking about the dials and start thinking about the job, which is the point. (The daily-workflow chapter turns exactly this two-profile habit into a default you don’t reconsider per task.)

A profile can also carry model and reasoning settings, not just trust - that’s a later chapter’s concern; here it’s purely the two axes plus the workspace-write block. The full list of what a profile can hold, and where its layer file has to live, is in the advanced config reference.

There is a single flag that throws both dials to their loosest at once: --dangerously-bypass-approvals-and-sandbox, which also answers to the alias --yolo.

codex --dangerously-bypass-approvals-and-sandbox
codex --yolo # the same thing, and the name is honest

It is never × danger-full-access in one switch - never being the approval policy that never pauses, danger-full-access the sandbox level with no fence at all. No approval checkpoint and no sandbox wall. The agent runs every action without asking, with read and write access to your whole filesystem and the network wide open. It is the genuinely hazardous corner of the two-axis grid, packaged as a convenience.

On a machine holding anything you care about, this is never the right call, and it’s worth being blunt about why. Your repo - and the data directory beside it - stands for your real accounts and your real history. --yolo simultaneously removes the only OS-level guarantee protecting that data and the only checkpoint where you’d catch a bad move before it lands. Every protection the two dials give you, gone in one flag.

The flag exists for environments that are already isolated - a throwaway container, a CI runner with nothing of yours in it, where the machine itself is the fence. Your laptop is the opposite. When a task feels like it needs --yolo, the move you actually want is to narrow the ask: open network_access for the network, or grant the one directory with --add-dir or writable_roots, rather than unlocking everything. The bypass flag’s own name is the warning; take it at its word. The precise behavior is in the agent approvals and security docs.

With a profile or two written, the agent is properly fenced for each kind of work you named: you can hand it a whole task without watching every step, because you’ve decided - deliberately - how much it can do and how far it can reach, and bundled each decision into a name you summon with one flag. That’s the shape of the whole approach: a single fused trust knob cannot say “ask at the fence, but keep the fence tight” - the two dials this page’s opening defined can - and a profile is how you stop paying for the second time you set them.

Notice, though, what the fence did not fix. A sandbox decides how much a wrong guess is allowed to cost. It has no opinion about whether the guess is wrong. Say the repo’s AGENTS.md - the instruction file Codex auto-loads at every session start - says “store money as integer cents, never floats,” and the importer does int(float(row["amount"]) * 100) anyway, with that rule sitting in context on every run. The wall doesn’t blink; the buggy edit lands inside the fence.

The sandbox stops an agent from doing damage; it has never once stopped it from being wrong - and neither, it turns out, will a rules file it merely reads. So what is a rules file actually worth? What guidance earns, what it costs, and where enforcement really has to live - that is the next chapter.