The Forge · 10 min mission
Codex CLI & config.toml: Setup That Sticks
Configure approvals, sandboxing, profiles, and MCP — once, correctly.
On this page
- Why config.toml is the whole game
- Sandbox modes: what the agent can do
- Approval policy: when it stops to ask
- The [sandboxworkspacewrite] table
- Profiles: a posture per file
- MCP servers in Codex
- A setup that sticks
- Commands and flags: the file is only half the story
- Inline overrides and the precedence ladder
- Sessions: resume, fork, replay
- Permission profiles: the modern alternative to sandboxmode
Codex is OpenAI's coding agent that runs locally on your machine — it reads your code, edits files, and runs commands from the terminal rather than from a chat window in a browser. The CLI is fast to install and friendly on day one, but its real personality lives in a single file: ~/.codex/config.toml. That file decides how much the agent can touch, when it stops to ask you, and which tools it can reach.
This guide takes you from a clean install to a setup that sticks — defaults you can trust on a Tuesday afternoon, profiles you can switch into when the job changes, and a security mindset that keeps a convenient install from becoming a stolen credential. Everything here is checked against OpenAI's official Codex documentation.
Why config.toml is the whole game
Most agent mistakes are not "the model was dumb." They are "the agent had more permission than the task needed." Codex separates two ideas that beginners tend to blur: the sandbox, which is the hard technical boundary on what the agent can do, and the approval policy, which decides when it pauses to ask. config.toml is where you set both. Get this file right once and every future session inherits sane behavior; get it wrong and you are either babysitting approvals all day or handing the agent the keys to your whole filesystem.
Install from an official source — and only an official source
Pick one official installer
Use the npm package
npm install -g @openai/codex, Homebrewbrew install --cask codex, or the standalone scriptcurl -fsSL https://chatgpt.com/codex/install.sh | sh. These are the methods OpenAI documents. The package name is exactly@openai/codex— the scope matters (see the security section on why).Authenticate on first run
Launch
codexin any project directory. The first run prompts you to sign in with your ChatGPT account (Plus, Pro, Business, Edu, and Enterprise plans include Codex) or with an API key.Let the config file be created
Codex stores user-level configuration at
~/.codex/config.toml. You can also keep a project-scoped.codex/config.tomlin a repo. If the file does not exist yet, create it — every setting in this guide lives there.
Sandbox modes: what the agent can do
The sandbox is the boundary Codex cannot cross even if it decides to. There are three modes, set with the sandbox_mode key:
read-only — Codex can inspect files but cannot edit them or run commands without approval. This is the safe default for exploring an unfamiliar repo, code review, or any session where you only want answers, not changes.
workspace-write — Codex can read files, edit within the workspace, and run routine local commands inside that boundary. This is the low-friction mode for everyday development. Crucially, network access is off by default here: in the default permission posture, Codex asks before using the internet or reaching beyond the workspace. You opt network back in explicitly (shown below), which is exactly the kind of decision you want to make on purpose rather than by accident.
danger-full-access — Codex runs with no sandbox restrictions at all: the filesystem and network boundaries are removed. The name is a warning, not decoration. Reserve it for throwaway containers and CI runners you fully control, never your primary machine.
Approval policy: when it stops to ask
The sandbox decides capability; approval_policy decides interruption. The documented values are untrusted (Codex runs only commands it considers trusted and asks for everything else), on-request (the agent runs what it can within the sandbox and pauses to ask when it wants to step outside the boundary), and never (it never pauses to ask — pair this only with a sandbox tight enough that "never asking" is still safe). Codex also supports a granular object form of approval_policy for fine-grained control, but the three string values cover the cases most people need.
The mental model: sandbox is the fence, approval is the doorbell. A tight fence with a loud doorbell (read-only + on-request) is cautious. A wide fence with no doorbell (danger-full-access + never) is how people lose an afternoon to an agent that rm -rf'd the wrong directory.
| Key | Value | Effect |
|---|---|---|
sandbox_mode | "read-only" | Inspect files only; edits and commands need approval |
sandbox_mode | "workspace-write" | Edit inside the workspace and run local commands; network off by default |
sandbox_mode | "danger-full-access" | No filesystem or network boundary at all — containers only |
approval_policy | "untrusted" | Run only trusted commands; ask for everything else |
approval_policy | "on-request" | Work within the sandbox; pause to ask before stepping outside it |
approval_policy | "never" | Never pause to ask — only safe with a tight sandbox |
# Default model and behavior for every session
model = "gpt-5.5"
approval_policy = "on-request"
sandbox_mode = "workspace-write"
# Editing is allowed in the workspace, but the agent must ask
# before it reaches the network. Opt in explicitly when needed.
[sandbox_workspace_write]
network_access = false
# writable_roots = ["/extra/path/the/agent/may/write/to"]The [sandbox_workspace_write] table
When you choose workspace-write, the [sandbox_workspace_write] table lets you tune the edges of that boundary. The most consequential key is network_access (a boolean): set it to true only when the task genuinely needs the internet — installing dependencies, calling an API — and prefer to grant it inside a dedicated profile rather than your global default. The table also accepts writable_roots, an array of additional paths the agent may write to beyond the workspace, plus toggles like exclude_slash_tmp and exclude_tmpdir_env_var for controlling whether temporary directories stay writable. Keep this table small. Every path you add and every boolean you flip to true is a piece of the fence you just removed.
Profiles: a posture per file
You rarely want a single permission posture. Reviewing a stranger's pull request wants read-only; an unattended CI job wants no approval prompts; refactoring your own repo wants the comfortable middle. Profiles let you keep each of these postures in its own file and switch between them per invocation.
You define each profile in its own file at ~/.codex/NAME.config.toml, using the same top-level keys you would put in config.toml — not nested under a [profiles.NAME] table. Select one at launch with --profile NAME (or the shorthand -p NAME): Codex loads ~/.codex/config.toml first, then overlays the chosen profile file on top. Settings resolve by precedence — command-line arguments win over the active profile, which wins over the top-level config.toml defaults — so your top-level keys are the "if I say nothing" behavior, and each profile is a deliberate override you opt into by name.
(Heads up if you are migrating an older setup: as of Codex 0.134.0 the in-config [profiles.NAME] table syntax was removed — --profile no longer reads it — so move each block into its own ~/.codex/NAME.config.toml file.)
# ~/.codex/config.toml — the cautious default when you pass no flags
approval_policy = "on-request"
sandbox_mode = "workspace-write"
# ~/.codex/review.config.toml — inspect a branch you do not trust, change nothing
approval_policy = "untrusted"
sandbox_mode = "read-only"
# ~/.codex/ci.config.toml — unattended runner, no human to answer prompts
approval_policy = "never"
sandbox_mode = "workspace-write"
# ~/.codex/deps.config.toml — refactor that needs to hit the network on purpose
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
# invoke a posture by name: codex --profile review (or codex -p ci)MCP servers in Codex
Codex can call external tools through the Model Context Protocol (MCP) — the same open standard other agents use to reach databases, browsers, issue trackers, and custom internal tools. You wire a server in with a [mcp_servers.NAME] table. The keys are the launch recipe: command is the executable, args is an array of arguments, env is a table of environment variables to pass, and startup_timeout_sec bounds how long Codex waits for the server to come up.
A practical note that doubles as a security note: an MCP server is a program Codex will execute on your behalf. Adding one is closer to installing a plugin than editing a setting. Pin it to a specific, trusted package, read what it does, and pass secrets through env rather than hard-coding them in arguments that might land in logs.
A setup that sticks
Put it together and the workflow is calm. Your top-level config.toml is conservative — workspace-write plus on-request, network off. Named profiles capture the exceptions: review for untrusted code, ci for unattended runs, deps for the rare network task. MCP servers are added deliberately, one trusted package at a time, with secrets in env. And every install comes from @openai/codex or another method OpenAI documents — never a lookalike. The payoff is that you stop thinking about permissions during the work: you decided the boundaries once, in a file you can read top to bottom, and now the agent operates inside them by default.
[mcp_servers.docs]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
startup_timeout_sec = 20
[mcp_servers.github]
command = "github-mcp-server"
args = ["--read-only"]
env = { GITHUB_TOKEN = "ghp_…" }Commands and flags: the file is only half the story
config.toml sets the standing posture; the command line sets the posture for this one run. Most of what you tune in the file has a flag that overrides it for a single invocation, and a handful of flags have no file equivalent at all. [V] Every flag below is a documented global option on the codex command — combine them freely, and remember that anything you pass here outranks every file.
| Flag | Long form | What it does |
|---|---|---|
-m | --model | Pick the model (and reasoning level) for this run — overrides model [V] |
-s | --sandbox | Set the sandbox for this run: read-only, workspace-write, or danger-full-access — overrides sandbox_mode [V] |
-a | --ask-for-approval | Set the approval mode (untrusted / on-request / never) — overrides approval_policy [V] |
-C | --cd | Run as if started from a different directory — sets the workspace root [V] |
-i | --image | Attach an image (screenshot, design spec) to the prompt [V] |
-p | --profile | Load a named profile file ~/.codex/NAME.config.toml on top of config.toml [V] |
-c | --config | Override an arbitrary config key inline, e.g. -c approval_policy=never — highest precedence of all [V] |
— | --add-dir | Grant the session an additional writable directory beyond the workspace [V] |
— | --oss | Run against a local open-source model instead of an OpenAI-hosted one [V] |
— | --search | Enable the web-search tool for this session [V] |
— | --remote | Run the task on Codex cloud infrastructure rather than locally [V] |
— | --yolo | Dangerously bypass *both* approvals and the sandbox — equivalent to danger-full-access + never, containers only [V] |
Inline overrides and the precedence ladder
The -c / --config flag is the escape hatch: it sets any config key for one run using TOML syntax, with dot notation for nested tables. [V] The value is parsed as TOML, so strings need TOML quoting — which is why a string value often carries an inner pair of quotes:
codex -c approval_policy=never -c sandbox_mode=workspace-write— flip both dials for a single run.codex --config model='"gpt-5.4"'— the inner"…"is the TOML string; the outer'…'keeps your shell from eating it. [V]codex -c sandbox_workspace_write.network_access=true— dotted path reaches into a table. [V]codex --config 'shell_environment_policy.include_only=["PATH","HOME"]'— values can be arrays too. [V]
When the same key is set in more than one place, Codex resolves it top-down and the first hit wins. [V] From highest priority to lowest:
- CLI flags and
-coverrides — what you typed this run. - Project config —
.codex/config.tomlfiles from the repo root down to your cwd (closest wins; trusted projects only). - Profile file selected with
--profile NAME(~/.codex/NAME.config.toml). - User config —
~/.codex/config.toml. - System config —
/etc/codex/config.tomlon Unix, if present. - Built-in defaults.
The practical read: a flag always beats the file, a trusted project's .codex/config.toml beats your personal profile, and your global defaults are the floor that everything else stands on. Note the trust gate on layer 2 — an untrusted project's .codex/ directory is skipped entirely (see trust_level below), so a hostile repo cannot silently lower your guardrails by shipping its own config.
Sessions: resume, fork, replay
Every interactive run is recorded as a rollout transcript — newline-delimited JSON written under ~/.codex/sessions/. [V] That history is what makes a session resumable instead of disposable:
codex resume— pick from recent sessions interactively. [V]codex resume --last— jump straight back into the most recent one. [V]codex resume --last --all— widen the search beyond the current directory to every recorded session. [V]codex resume <SESSION_ID>— reopen a specific session by its id. [V]codex fork— branch from an existing session so you can explore an alternative path without overwriting the original transcript. [V]
Because the transcript is plain JSONL on disk, it is also auditable: you can read exactly what the agent did, which is the other half of why keeping sessions tight (and out of untrusted hands) matters.
| Key | Type | What it controls |
|---|---|---|
model_context_window | number | Context-window tokens available to the active model [V] |
model_auto_compact_token_limit | number | Token threshold that triggers automatic history compaction; unset uses the model default [V] |
[history] max_bytes | number | Caps history.jsonl size by dropping the oldest entries [V] |
[history] persistence | save-all \| none | Whether session transcripts are saved to history.jsonl at all [V] |
[shell_environment_policy] inherit | all \| core \| none | Baseline environment the agent inherits when it spawns a subprocess [V] |
[shell_environment_policy] exclude | array<glob> | Variables stripped *after* the defaults; defaults already drop names matching KEY/SECRET/TOKEN [V] |
notify | array<string> | External command Codex invokes for notifications, passed a JSON payload [V] |
projects.<path>.trust_level | trusted \| untrusted | Marks a path trusted; an untrusted project skips all project-scoped .codex/ layers (config, hooks, rules) [V] |
Permission profiles: the modern alternative to sandbox_mode
sandbox_mode plus [sandbox_workspace_write] is the original, coarse model — one fence with a few toggles. The newer permission profiles replace it with a named policy that combines filesystem rules (what commands may read or write) and network rules (which destinations they may reach), expressed as glob-matched allow / write / read / deny entries where the most specific rule wins and deny beats write beats read. [V]
You turn the system on by setting default_permissions to a profile name. Three presets are built in: :read-only (execution can read but not write), :workspace (writes allowed inside the active workspace roots and temp dirs), and :danger-full-access (local sandbox removed). [V] Custom profiles live under [permissions.<name>] and should extends a preset so baseline protections carry forward instead of being restated. [V]
The one rule that trips people up: permission profiles do not compose with the old sandbox keys. [V] Configure either default_permissions + [permissions], or sandbox_mode / [sandbox_workspace_write] — never both in the same config. Pick the model you want and commit to it.
Permission simulator
Permission simulator
Pick an action and a permission mode. The engine runs the same deny → ask → allow precedence Claude Code uses and reveals the verdict — with the one rule that fired.
Editing a file is a modification — default mode prompts you to approve it first.
# ~/.codex/config.toml — opt into the permission model (not sandbox_mode)
default_permissions = "locked-edit"
# Inherit :workspace, then carve out the dangerous bits
[permissions.locked-edit]
extends = ":workspace"
# Writable workspace, but never let the agent read or write secret files
[permissions.locked-edit.filesystem.":workspace_roots"]
"." = "write"
"**/*.env" = "deny"
# Network is off until you name the exact hosts that are allowed
[permissions.locked-edit.network]
enabled = true
[permissions.locked-edit.network.domains]
"api.openai.com" = "allow"
"*.github.com" = "allow"
# everything not listed is denied by omission# requirements.toml is the enforcement layer. List the profiles a machine is
# allowed to use; once present, any profile NOT listed is denied — including
# omitted built-ins and profiles added in future Codex versions.
allowed_permission_profiles = ["locked-edit", ":read-only"]
# Refuse hooks unless they come from managed, admin-approved configuration.
allow_managed_hooks_only = trueKnowledge check
You want Codex to refactor your own repo and run the test suite, but you do NOT want it touching the network or running unattended. Which config.toml pairing fits best?
Reach the end and this star joins your charted sky.