Blog

Codex CLI Config by Scenario: Local, CI, Profiles, and MCP

Four scenarios, a handful of lines each: local read-only work, networked development, unattended CI, and the profile and MCP settings that changed recently.

Codex CLI Config by Scenario: Local, CI, Profiles, and MCP

Most Codex configuration advice fails for the same reason: it lists fields without saying which situation each field is for. The four situations below cover most real work, and each one needs only a few lines. Before any of them, though, clear two migration traps that will waste an afternoon if you skip them.

The first is approval_policy = "untrusted". Codex no longer supports that value, and the retired setting can prevent the client from starting. The second is the profile mechanism, which changed in 0.134.0: --profile no longer reads [profiles.profile-name] from config.toml, and the top-level profile = "profile-name" selector is no longer supported.

Everything below follows the current Codex manual, checked on 2026-10-28. Verify field names and values against that page before changing a shared configuration.

Clear the two migration traps first

The retired approval policy is the more urgent of the two because it can stop the client from launching at all. The manual's instruction is to remove it from user or project configuration, profile files, startup scripts, and managed defaults. The supported replacement for interactive, read-only work is a pair of lines:

sandbox_mode = "read-only"
approval_policy = "on-request"

The CLI equivalent is codex --sandbox read-only --ask-for-approval on-request.

The profile change is quieter. Since 0.134.0, a profile is a separate file, ~/.codex/profile-name.config.toml, layered over config.toml. A [profiles.profile-name] table still sitting in config.toml is simply not read, and neither is the old top-level selector. The migration is mechanical, but it has to be done.

Scenario: local read-only work

This is the configuration the manual recommends for interactive sessions where the agent should look but not touch. sandbox_mode = "read-only" constrains what the process can reach, and approval_policy = "on-request" decides when it must stop and ask.

The semantics of on-request are worth stating precisely, because they are frequently misread as "ask before everything". In the manual's wording, commands allowed by the sandbox can run without approval, read accessible files, and use network access if enabled. The approval prompt appears when an action needs to go beyond what the sandbox already permits.

If you want the stricter command-approval behavior, do not set approval_policy at all. Instead add a project entry to your user-level ~/.codex/config.toml:

[projects."/path/to/project"]
trust_level = "untrusted"

Commands then require approval unless an execution-policy rule allows them. Two consequences are easy to miss: this also disables project-local configuration for that project, and explicitly setting on-request overrides the project-derived policy. In a managed environment, allowed_approval_policies must include untrusted for this to be permitted at all.

Scenario: local development that needs the network

The default workspace-write sandbox keeps network access turned off. That single fact explains the most common complaint in this category, which is a build step that cannot download dependencies while everything else looks correctly configured.

The fix is explicit, not implied:

[sandbox_workspace_write]
network_access = true

Enable it only where the work actually needs it. A development sandbox that can reach the network can also exfiltrate what it reads, which is why the default is closed.

Scenario: CI and unattended runs

This is where configuration most often turns into a failed build. The manual is direct: in non-interactive flows, or whenever a run cannot surface a fresh approval, an action that needs new approval fails and Codex surfaces the error back to the parent workflow. A pipeline cannot wait for a human to click approve, so a setting that felt safe interactively becomes a hard failure in automation.

There are two legitimate ways to handle it, and both are permission decisions rather than performance tweaks.

The narrower one is a granular approval policy: approval_policy = { granular = { ... } }. It lets you keep specific approval prompt categories interactive while automatically rejecting others, covering sandbox approvals, execpolicy-rule prompts, MCP prompts, request_permissions prompts, and skill-script approvals. In an unattended run the categories you keep interactive cannot surface a prompt, so an action that needs one of them fails rather than waiting, and the categories you set to auto-reject fail immediately. That is useful for bounding what a pipeline may attempt; it is not a way to auto-approve anything, because every category either prompts or rejects.

The broader one is to remove the sandbox and approval boundary explicitly, with --sandbox danger-full-access or --dangerously-bypass-approvals-and-sandbox. The manual's guidance is to use caution before doing so, and that caution is the point: this is the option that lets a compromised dependency do whatever the runner can do. Use it only where the outer environment already provides the isolation, such as a disposable container, and treat it as an explicit risk acceptance.

The CI patterns themselves, including how to keep agent runs reproducible, are covered in the coding agents in CI guide. Credentials deserve the same treatment as permissions; the rotation guide covers swapping them without breaking a session.

Scenario: multiple environments with profiles

After the migration, profiles are files rather than tables. Passing --profile profile-name loads ~/.codex/config.toml and then overlays ~/.codex/profile-name.config.toml. Names can contain letters, numbers, hyphens, and underscores.

Three rules make the file behave predictably. Use top-level keys; do not nest them under [profiles.profile-name]. Include only the values that differ from your base configuration, because the profile is a layer above the user config and below project and CLI config. And remember that a profile can override model_catalog_json; when both files set it, the profile wins. Which layer takes precedence overall is the subject of the config precedence guide, and the provider block itself is described in the custom provider guide.

A migrated profile file is short by design:

# ~/.codex/deep-review.config.toml
model_reasoning_effort = "medium"
approval_policy = "on-request"

Then codex --profile deep-review selects it for a session, and the same flag works for non-interactive runs.

Scenario: shared project configuration and its limits

A repository can carry .codex/config.toml, but two conditions govern it. Codex loads project-scoped config files only when you trust the project, and project-scoped config cannot override machine-local keys.

The ignored list is specific: openai_base_url, chatgpt_base_url, apps_mcp_product_sku, model_provider, model_providers, notify, profile, profiles, experimental_realtime_ws_base_url, and otel are all ignored when they appear in a project-local file. Provider, notification, and telemetry keys belong in the user-level config. This is the actual explanation for "we configured the provider in the repo and it does not take effect", and no amount of re-editing the project file will change it.

What a project file is good for is the part that is genuinely repository-specific: sandbox and approval behavior for that codebase, and other non-machine-local settings. Keep the split intentional, because a project file that looks authoritative but is silently ignored is worse than no project file at all.

Scenario: wiring MCP servers

MCP servers are declared under [mcp_servers.<id>], and the fields that matter for operability are a small set. command is the launcher for a stdio server, with args for its arguments and env for environment variables forwarded to it. A remote server uses url for a Streamable HTTP endpoint. enabled turns a configured server off without deleting its block, and startup_timeout_sec overrides the startup timeout.

The field to think hardest about is required. When true, Codex fails startup or resume if that enabled server cannot initialize. That is the correct setting for a server the session genuinely cannot function without, and the wrong setting for a convenience integration: one flaky optional server should not be able to stop every session from starting. The audit questions for any server, including what it can read and what it can do, are in the MCP server guide.

Scenario: parallel subagents

Global subagent behavior lives under [agents]: agents.enabled, agents.max_concurrent_threads_per_session, agents.default_subagent_model, agents.default_subagent_reasoning_effort, and agents.interrupt_message.

Custom agents are standalone TOML files under ~/.codex/agents/ for personal use or .codex/agents/ for project scope, one agent per file. Every file must define name, description, and developer_instructions. Codex identifies the agent by the name field, not the filename.

Two resolution rules prevent surprises. Settings resolve from an explicit spawn value, then the corresponding [agents] default, then the parent's value; if a custom agent file sets model or model_reasoning_effort, that file value takes precedence for those keys. Everything else about permissions is inherited: sandbox_mode, approval_policy, model_provider, and mcp_servers in an agent file do not override the parent session, and the service tier follows the root session. Configure MCP servers on the parent if you want subagents to use them.

Model behavior keys

Three keys cover most of the model-side tuning. model_reasoning_effort selects the reasoning effort advertised by the chosen model. model_reasoning_summary accepts auto, concise, detailed, or none, and controls summary detail or disables summaries. model_verbosity accepts low, medium, or high as an optional verbosity override for the Responses API.

Set these per scenario rather than globally where the workload differs. A review profile and a quick-edit profile rarely want the same effort, and the profile file is the natural place to express that difference.

The checklist

  1. Search every config source for untrusted and remove the retired approval policy.
  2. Search for [profiles. tables and the top-level profile selector; move their content into ~/.codex/profile-name.config.toml.
  3. Confirm that CI has no path requiring a fresh interactive approval, or bound it with a granular policy.
  4. Enable sandbox_workspace_write.network_access only where a build genuinely needs the network.
  5. Check project files for provider, notification, or telemetry keys, and move them to user-level config.
  6. Reserve required = true for MCP servers the session cannot run without.
  7. Do not rely on sandbox or approval keys inside custom agent files; set them on the parent.
  8. Verify the result with a real request, and read failures with the triage map before editing more config.

Configuration is a description of intent, and every scenario above fails in a different way when the intent and the environment disagree. The two migration traps are the ones that fail immediately; the rest fail quietly, which is why the checklist ends with a real request rather than a reread of the file.

All field names and behaviors above follow the official manual as checked on 2026-10-28; confirm them against the current documentation before relying on them.