Agentic AI AcademyAgentic AI Academy

config.toml, Precedence & Profiles

Tuning Codex with layered configuration

Intermediate 13 minBuilder
What you'll be able to do
  • Locate the three `config.toml` layers (user, project, system) and explain how `CODEX_HOME` relocates them
  • Resolve a setting by walking the full precedence chain from CLI flags down to built-in defaults
  • Override any config key for a single run with `-c key=value` and know which keys a project config can never touch
  • Configure project trust so an untrusted repo's `.codex/` layer is skipped
  • Set the core keys you'll actually reach for — model, reasoning effort, approvals, sandbox, and MCP servers
  • Create and select a standalone profile overlay (`~/.codex/<name>.config.toml`) with `--profile`, and avoid the deprecated inline `[profiles.NAME]` form
At a glance

Codex reads its settings from a stack of `config.toml` files — system, user, project, and profile overlays — resolved by a strict precedence chain, with CLI flags on top. This lesson teaches you to configure Codex *durably*: where each file lives, who wins when they disagree, how to override any key for a single run, why a repo you don't trust can't quietly reconfigure your agent, and how standalone profiles let you swap whole postures with one flag.

  1. 1The mental model: a stack of overlays, not one file
  2. 2The three file locations
  3. 3The precedence chain: who wins
  4. 4Override any key for one run: `-c`
  5. 5Project trust: gating the repo's config layer
  6. 6The core keys you'll actually set
  7. 7Profiles: swap a whole posture with one flag

The mental model: a stack of overlays, not one file

Most tools have a config file. Codex has a stack of them, and the single most useful idea in this lesson is to stop thinking "where is the config file?" and start thinking "which layers apply, and in what order do they win?"

Codex resolves every setting by layering several config.toml files on top of each other, then letting anything you typed on the command line override the result. Picture a stack of transparency sheets: a system-wide default at the bottom, your personal defaults above it, an optional per-project file above that, an optional named profile overlay, and your live CLI flags on the very top. To read any setting, you look down through the stack and take the first sheet that defines it.

This is the same idea you already met with AGENTS.md — global guidance layered with project overrides — but applied to machine behavior (which model, when to ask permission, what the sandbox allows) rather than prose instructions. The two systems are complementary: AGENTS.md tells Codex how to work on this project; config.toml tells Codex how to run.

Everything Codex needs lives under one home directory — ~/.codex/ by default — holding config.toml, your credentials (auth.json), profiles, sessions, and history. Relocate the whole thing by setting the CODEX_HOME environment variable, which is exactly how you keep work and personal setups separate or pin a clean config in CI.

Key insight

One sentence to remember

Codex doesn't read one config file — it merges a stack of them by precedence, so the right question is never "where's the config?" but "which layer wins for this key?"

Note

CODEX_HOME relocates the whole home

Setting CODEX_HOME=/path/to/dir moves config.toml, the global AGENTS.md, profile files, auth.json, sessions, and history together. Useful for isolating a CI config or running two separate Codex identities on one machine.

The three file locations

There are three places a config.toml can live, each scoped differently. Learn the scope, not just the path — the scope is what determines the precedence later.

LayerPathScopeNotes
User~/.codex/config.tomlYour personal defaults on this machineThe one you'll edit most. Relocatable via CODEX_HOME.
Project.codex/config.tomlThis repository onlyLoaded only for trusted projects (see §4). Closest directory wins in a monorepo.
System/etc/codex/config.tomlEvery user on the machineLowest non-default layer — for org/admin-managed baselines.

The user file is your home base: set your preferred model, default approval posture, and MCP servers here once and every project inherits them. The project file lets a repo ship its own settings — say, a tighter sandbox or a repo-specific MCP server — so contributors get consistent behavior without each configuring it by hand. The system file is where an admin pins machine-wide policy.

A config.toml is plain TOML: top-level key = value pairs and [table] sections. You don't need to create every file — Codex falls back to built-in defaults for anything no layer defines, so a brand-new install with no config.toml at all still runs.

Tip

Project file lives in `.codex/`, not the repo root

It's .codex/config.toml (a folder), not a bare config.toml. In a monorepo, open the directory that holds the .codex folder, and remember the closest project file to your working directory wins.

The precedence chain: who wins

When two layers set the same key, Codex needs a tiebreaker. That tiebreaker is a fixed precedence chain. From highest priority (wins) to lowest (loses):

  1. CLI flags & -c/--config overrides — what you typed this run
  2. Project config.codex/config.toml, closest directory wins
  3. Profile file — the --profile overlay (covered in §7)
  4. User config~/.codex/config.toml
  5. System config/etc/codex/config.toml
  6. Built-in defaults — Codex's baked-in fallbacks

Read it top-down: Codex takes the value from the highest layer that defines the key, and lower layers fill in only the keys nobody above them set. So your user config sets model = "gpt-5.5", a project pins sandbox_mode = "read-only", and a single -m gpt-5.4 on the command line beats both for the model — while the project's sandbox setting still stands because nothing higher overrode it.

The ordering is deliberately intuitive: the more specific and immediate the source, the more authority it has. A flag you typed for one run beats a file you wrote once; a per-repo file beats your global default; your global default beats the admin baseline. This is exactly why you can keep sane defaults in your user config and still steer any individual run without editing files.

text
  CLI flags / -c overrides        (highest — this run only)
  project .codex/config.toml      (closest dir wins)
  --profile overlay file
  user ~/.codex/config.toml
  /etc/codex/config.toml
  built-in defaults               (lowest — fallback)

Example

Worked resolution

User config: model = "gpt-5.5", approval_policy = "on-request". Project .codex/config.toml: sandbox_mode = "read-only". You run codex -a never. Result: model gpt-5.5 (from user), sandbox read-only (from project), approval policy never (the CLI flag beat the user file). Each key resolves independently down the chain.

Override any key for one run: `-c`

Most settings have a dedicated flag — -m/--model, -s/--sandbox, -a/--ask-for-approval. But for the long tail of keys that don't have a shortcut, there's a universal escape hatch: -c key=value (long form --config key=value), repeatable, which sets any config key for that single invocation without touching a file.

bash
# Send this run's logs to a project-local dir, just this once
codex -c log_dir=./.codex-log

# Stack several one-off overrides
codex -c model_reasoning_effort=high -c sandbox_mode=read-only

# Works with the headless runner too
codex exec -c model=gpt-5.4 "summarize the failing tests"

Because -c sits at the top of the precedence chain, it overrides every file layer. It's the right tool when you want to try a setting before committing it to a file, or run one task under a different posture without disturbing your durable config.

There is, however, a hard boundary the override system enforces. A project config (and therefore a repo you cloned) cannot override machine-local keys: provider/auth configuration, notification settings, profile selection, and telemetry routing all stay under your control. This is a security boundary, not an oversight — without it, cloning a hostile repo could silently repoint your model provider at an attacker's endpoint or exfiltrate via a notification hook. Those keys can only come from your user or system config, or your own CLI flags.

Watch out

Project config can't touch your auth or provider

Keys controlling the model provider, credentials, notifications, profile selection, and telemetry routing are machine-local — a .codex/config.toml you pulled from a repo cannot set them. Treat this as load-bearing security: it's why opening an untrusted project can't quietly reconfigure how Codex authenticates or where it sends data.

Project trust: gating the repo's config layer

There's a subtle risk in the precedence chain: a project's .codex/config.toml ranks above your user config. If Codex blindly honored every repo's config, cloning a sketchy project could change your agent's behavior the moment you opened it. Project trust is the gate that prevents this.

Project-scoped .codex/ layers load only for trusted projects. You mark trust explicitly in your user config:

toml
[projects."/Users/you/work/payments-api"]
trust_level = "trusted"     # "trusted" | "untrusted"

The key is the absolute path to the repo. When a project is untrusted, Codex skips its project-scoped .codex/ config layer entirely — your user and system config still load, but the repo's own settings are ignored. So an untrusted repo runs with your defaults, never settings it shipped.

The practical workflow: trust your own repos and your team's known projects so their .codex/config.toml and project AGENTS.md take effect; leave anything you just cloned from the internet untrusted until you've actually read what its .codex/ folder contains. Combined with the machine-local key boundary from §5, trust gives you two layers of defense — even a trusted project still can't touch your auth or provider keys.

Key insight

Two boundaries, defense in depth

Trust decides whether a repo's .codex/ layer loads at all; the machine-local boundary decides which keys a project layer may set even when it does. An untrusted repo's config is skipped wholesale; a trusted repo's config still can't repoint your provider or auth.

The core keys you'll actually set

The config reference is long, but in practice you reach for a small set. Here are the keys worth memorizing — the same ones you've been setting with flags in earlier lessons, now made durable. Model names, the default model, and per-model defaults are volatile — confirm the current values on the live models page rather than trusting any hard-coded value below.

toml
# ── Model & reasoning ──
model = "gpt-5.5"                  # leave UNSET on ChatGPT auth to track the recommended default
model_reasoning_effort = "medium" # minimal | low | medium | high | xhigh (Responses-API models only)
model_reasoning_summary = "auto"  # auto | concise | detailed | none

# ── Approvals & sandbox (the two independent controls) ──
approval_policy = "on-request"    # untrusted | on-request | never   (WHEN Codex asks)
sandbox_mode = "workspace-write"  # read-only | workspace-write | danger-full-access   (WHERE it can act)

[sandbox_workspace_write]
network_access = false            # network is OFF by default in workspace-write — opt in here
writable_roots = ["/extra/path"]  # grant extra writable dirs

# ── MCP servers (one table per server) ──
[mcp_servers.fs]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/repo"]

A few things to internalize:

  • approval_policy and sandbox_mode are independent. Approvals decide when Codex stops to ask you; the sandbox decides where Codex is allowed to act. You set them separately — a repo can be read-only but never ask, or workspace-write but ask on-request.
  • On ChatGPT-account auth, leaving model unset is the recommendation — Codex then tracks the current recommended default instead of pinning you to a name that may get retired.
  • model_reasoning_effort applies only to Responses-API models, and its per-model default varies — so omit it unless you specifically want to dial reasoning up or down.
  • [mcp_servers.<id>] registers a tool server; the <id> is your label. You'll usually add these with codex mcp add (covered in the MCP lesson), which writes this same table for you.

Watch out

Don't hard-code volatile values

Model IDs (gpt-5.x), the recommended default, the xhigh effort tier's supported models, and per-model reasoning defaults all change. Treat the snippet above as shape, not gospel — check developers.openai.com/codex/models and the config reference for current values, and prefer leaving model unset on ChatGPT auth.

Tip

Network is off in workspace-write

In workspace-write, outbound network is disabled by default. If a task needs to install dependencies or hit an API, set network_access = true under [sandbox_workspace_write] — and only for repos you trust.

Profiles: swap a whole posture with one flag

Editing config.toml every time you switch between careful review and fast autonomous work gets old fast. Profiles solve this: a profile is a named overlay of config that you activate with a single flag.

As of Codex 0.134.0+, a profile is a standalone file: ~/.codex/<name>.config.toml. Codex loads your base ~/.codex/config.toml first, then overlays the profile file on top — so a profile only needs to specify the keys it changes.

toml
# ~/.codex/deep-review.config.toml
model = "gpt-5.5"
model_reasoning_effort = "xhigh"
approval_policy = "on-request"

Select it with --profile (short -p), in both interactive and headless modes:

bash
codex --profile deep-review
codex -p deep-review
codex exec --profile deep-review "review the auth refactor for security regressions"

In the precedence chain (§3), the profile sits above your user config but below the project config and CLI flags — so a project .codex/config.toml or a one-off -c can still override what a profile sets. Profile names may use letters, numbers, hyphens, and underscores.

One deprecation to know: the older inline [profiles.NAME] table inside config.toml is deprecated as of 0.134.0+. If you have profiles defined as [profiles.deep-review] sections in your config.toml, migrate each to its own standalone ~/.codex/deep-review.config.toml file. This is exactly the kind of version-gated detail that rots — confirm the current syntax in the config-advanced docs and the changelog before relying on either form.

Example

Two profiles, two postures

Keep a deep-review profile (xhigh effort, on-request approvals, read-only sandbox) for auditing risky changes, and a fast profile (lower effort, workspace-write) for routine edits. Then codex -p deep-review vs codex -p fast flips your entire posture without editing a file.

Watch out

Inline [profiles.NAME] is deprecated

If your config.toml still has [profiles.foo] tables, they're on the deprecated path (0.134.0+). Move each into a standalone ~/.codex/foo.config.toml. Verify the current behavior on the live config-advanced page — version-gated syntax is the kind of fact that changes.

Try it: Build a layered config and a reusable profile

Goal: feel the precedence chain and profiles firsthand on a repo you own. 1) Inspect your home. Locate ~/.codex/config.toml (create it if absent). Add a couple of durable defaults you understand — e.g. approval_policy = "on-request" and model_reasoning_effort = "medium". Leave model unset if you're on ChatGPT auth. 2) Add a project layer. In a repo you trust, create .codex/config.toml and set one thing that differs, e.g. sandbox_mode = "read-only". Then trust the repo: add [projects."<absolute-path-to-repo>"] trust_level = "trusted" to your user config. 3) Prove precedence. Run codex in the repo and use /status to inspect the active configuration — confirm the sandbox came from the project layer while approvals came from your user layer. 4) Override for one run. Launch with codex -c model_reasoning_effort=high (or -a never) and re-check /status; watch the CLI override beat both files, then disappear on the next plain run. 5) Test the trust gate. Temporarily flip the project to trust_level = "untrusted" in your user config, relaunch, and confirm via /status that the project's .codex/ settings no longer apply. Set it back to trusted. 6) Make a profile. Create ~/.codex/deep-review.config.toml with model_reasoning_effort = "xhigh" and sandbox_mode = "read-only", then run codex --profile deep-review and confirm the overlay took effect. 7) Write three sentences: which key came from which layer, what -c changed for exactly one run, and how the profile differed from editing your base config. This builds the instinct that Codex config is a stack you reason about, not a single file you keep rewriting.

Key takeaways

  1. 1Codex merges a stack of `config.toml` layers — system, user, project, and profile overlays — plus live CLI flags, all under `~/.codex/` (relocatable with `CODEX_HOME`). The question is never "where's the config?" but "which layer wins for this key?"
  2. 2Precedence, high to low: CLI flags & `-c`/`--config` > project `.codex/config.toml` (closest dir) > profile file > user `~/.codex/config.toml` > `/etc/codex/config.toml` > built-in defaults. Each key resolves independently.
  3. 3Override any key for one run with `-c key=value` (e.g. `codex -c log_dir=./.codex-log`) — but a project config can never set machine-local keys (provider, auth, notifications, profile selection, telemetry).
  4. 4Project trust gates the repo's config: mark `[projects."<abs-path>"] trust_level = "trusted"` in your user config; untrusted projects skip their `.codex/` layer entirely while your user/system config still loads.
  5. 5Core keys you'll actually set: `model`, `model_reasoning_effort`, `model_reasoning_summary`, `approval_policy`, `sandbox_mode`, `[sandbox_workspace_write]`, and `[mcp_servers.<id>]` — with approvals (WHEN it asks) and sandbox (WHERE it acts) kept independent.
  6. 6Profiles (Codex 0.134.0+) are standalone overlay files `~/.codex/<name>.config.toml` selected with `--profile`/`-p`; the inline `[profiles.NAME]` table is deprecated. Model names and per-model defaults are volatile — check the live models page.

Quiz

Lock in what you learned

Check your understanding

0 / 4 answered

1.Your user config sets `model = "gpt-5.5"`. A trusted project's `.codex/config.toml` sets `model = "gpt-5.4"`. You launch with `codex -m gpt-5.3-codex`. Which model runs, and why?

2.You clone an unfamiliar repo from the internet. It contains a `.codex/config.toml` that tries to repoint the model provider to an unknown endpoint and set a notification hook. With default handling, what happens when you run `codex` in it?

3.You want to send this single run's logs to a project-local directory without changing any config file. What's the correct command?

4.You're on Codex 0.134.0+ and want a reusable 'deep review' configuration (high reasoning effort, read-only sandbox) you can switch on with one flag. What's the current, non-deprecated way to do it?

Go deeper

Hand-picked sources to keep learning