Codex CLI: The Settings Nobody Writes About

October 8, 2026 Updated: October 8, 2026 · By Taylor Yang · #English #AI Agents #Codex CLI

Field notes on the Codex CLI settings and flags that quietly decide whether your configuration works: strict config checking, AGENTS.md truncation, permission profiles, hooks, and subagent limits.

Most Codex advice is the same advice: write an AGENTS.md, pick a model, set the sandbox mode, move on. That is the floor, not the ceiling. Below it sits a layer of settings and flags that almost no tutorial mentions, most of which fail silently — no error, no warning, just a config key that quietly does nothing.

Everything in this article was checked against the official documentation on October 8, 2026, and every claim about local behaviour was tested on codex-cli 0.146.0. Where a claim comes from a community report instead, it is labelled as such.

The pattern underneath most of this: Codex problems are rarely model problems. They are usually harness problems where something was dropped without telling you.

If you are new to terminal agents, it helps to know the interface first — the Claude Code keyboard shortcuts guide covers the shared muscle memory that carries over here.

Your config may have been ignored the whole time

If you read one thing here, read this.

Codex silently ignores configuration fields it does not recognise. Misspell a key, use a name that was renamed in a later release, or carry a field over from an old version, and nothing tells you. The setting does not work, and you believe it does.

There is a flag for this. --strict-config makes Codex exit with an error when it meets a field the current version does not know. Most guides never explain its real value, which is that it is the only reliable way to answer the question “why is my configuration being ignored?”

I tested it on this machine. The local ~/.codex/config.toml contains:

[skills]
max_context_tokens = 2000

With --strict-config:

Error loading config.toml:
/Users/yep/.codex/config.toml:496:1: unknown configuration field `skills.max_context_tokens`
    |
496 | max_context_tokens = 2000
    | ^^^^^^^^^^^^^^^^^^

Without it, the same configuration produces no output at all. The file is loaded, the field is discarded, and the session starts normally.

The interesting part is that this key does exist in the current official configuration reference (“Token budget for the available-skills catalog. Defaults to 2% of the model’s context window”, with explicit values capped at 10,000 tokens). Version 0.146.0 simply does not know it yet. Nobody wrote the config wrong here. This is exactly the version drift that happens as Codex moves — and --strict-config is how you see it.

So the practical habit is short: after every CLI upgrade, run one strict-config command and clean up whatever it reports.

codex exec --strict-config --skip-git-repo-check "reply OK"

Two known caveats:

  1. --strict-config rejects entries that Codex Desktop writes. The desktop app injects [projects."<path>"] blocks into your ~/.codex/config.toml, and the CLI’s strict validation does not accept them (issue #33181, 0.144.1). If the CLI and Desktop share one config directory, check who wrote those blocks before turning this on.

  2. Fields the documentation advertises can still fail strict validation. The docs specify experimental_environment = "remote" for remote stdio MCP servers, but the stable CLI rejects it during strict loading; you need environment_id instead (issue #51481). Documentation and runtime drift apart, and strict mode is what exposes the gap.

AGENTS.md is the main event, and it fails silently three ways

AGENTS.md is Codex’s project instruction file — the equivalent of Claude Code’s CLAUDE.md. Most people use it for “use pnpm”, “tests live here”. The leverage is bigger than that.

Trap one: anything past 32 KiB is dropped without a warning

This is the most useful thing I learned while researching this. Someone found that Codex was ignoring core rules they had added, read the source, and found:

/// Maximum number of bytes of the documentation that will be embedded. Larger
/// files are *silently truncated* to this size so we do not take up too much of
/// the context window.
pub(crate) const PROJECT_DOC_MAX_BYTES: usize = 32 * 1024; // 32 KiB

There is no warning. Write a 40 KB AGENTS.md and the last 8 KB never reaches the model, and you will not find out. The reporter noted that Claude Code warns you at launch when CLAUDE.md is too large; Codex does not (issue #7138, 0.63.0).

Two ways to handle it:

# raise the per-file read limit
project_doc_max_bytes = 131072

# or name alternates and split the rules across files
project_doc_fallback_filenames = ["TEAM_GUIDE.md", ".agents.md"]

The better fix is to keep it short. AGENTS.md exists to prevent bad guesses, not to hold a release handbook. Move architecture notes into docs/ and link to them.

Trap two: nested AGENTS.md precedence is still unspecified

The AGENTS.md standard says the nearest file wins. Codex has not clearly implemented or explained this. The official tracker issue records structured feedback from customers including Wix (issue #12115).

In a monorepo this means rules in packages/api/AGENTS.md may simply never be read. The current workaround is to recall them explicitly in the root file — “if the work touches a package, read its AGENTS.md first.” Fragile, but it works (HN discussion, 2026-04).

Trap three: AGENTS.md may not be treated as governance

Users report agents acknowledging “I have read AGENTS.md” and then treating it as suggested information rather than binding rules, dropping old rules as soon as a new instruction arrives (issue #41704). This is community observation, not confirmed by OpenAI.

If you need hard constraints to hold, do not put all of them in AGENTS.md. The approach that works better in practice is a separate PLANS.md for acceptance criteria. One user kept seven all-caps markdown files, and Codex started taking sha256 checksums of before/after screenshots after refactors — a direct result of PLANS.md saying “capture before/after screenshots to prevent regressions” (HN, 2026-05).

One side effect worth knowing: globally injected AGENTS.md content pollutes chat search. Searching for a common word can return the same Markdown template line four times (issue #44498). Keep large formatting instructions out of your global AGENTS.md.

Permissions: the real mechanism is a profile, not three levels

Most people only know read-only / workspace-write / danger-full-access. There is also a named permission profile system for fine-grained file and network policy — and in managed environments it is the only mechanism that actually locks behaviour down.

Three profiles are built in: :read-only, :workspace, :danger-full-access. Custom ones look like this:

default_permissions = "project-edit"

[permissions.project-edit.workspace_roots]
"~/code/app" = true
"~/code/shared-lib" = true

[permissions.project-edit.filesystem]
":minimal" = "read"
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
".devcontainer" = "read"
"**/*.env" = "deny"

[permissions.project-edit.network]
enabled = true
[permissions.project-edit.network.domains]
"api.openai.com" = "allow"
"*.github.com" = "allow"
"tracking.example.com" = "deny"

The part people get wrong

Network restriction is a combination of three settings, not two:

network.enabledfeatures.network_proxyActual behaviour
falseeitherCommands cannot reach the network at all
truefalseCommands connect directly — the domain rules are not enforced
truetrueCommands go through the proxy and the rules apply; no allowlisted domain means everything is blocked

Writing domain rules does not turn the rules on. You must also set features.network_proxy = true, or every deny and allow above is decoration.

The domain syntax has four forms worth memorising: example.com matches the host exactly, *.example.com matches subdomains only, **.example.com matches the apex plus all subdomains, and deny always beats allow.

Project trust is bound to the permission layer

Untrusted projects skip project-scoped .codex/ layers entirely — project config, hooks, and rules — while user and system layers still load (config basics). Do not trust a repository you just cloned; that is equivalent to executing its config as your own.

Project-level config also has a hard-forbidden list. These keys are ignored with a startup warning: openai_base_url, model_provider, model_providers, notify, profile, profiles, and otel. Provider, notification, and telemetry settings belong in user-level ~/.codex/config.toml (advanced configuration).

Finally, on managed machines an explicit --dangerously-bypass-approvals-and-sandbox can still be overridden by an organisation-level read-only profile, with no explanation of where it came from (issue #51116). If a bypass flag appears ignored, suspect the managed policy.

Configuration keys the docs mention once

Not tricks — just documented briefly and almost never discussed.

notify wires turn completion into your own tooling

notify = ["python3", "/path/to/notify.py"]

The script receives one JSON argument containing type (currently only agent-turn-complete), thread-id, turn-id, cwd, input-messages, and last-assistant-message.

This covers anything the built-in TUI notification does not: desktop toasts, chat webhooks, CI updates, long-task alerts. It is what this machine uses for terminal notifications.

codex debug prompt-input shows what the model actually sees

codex debug prompt-input

It renders the model-visible prompt input list as JSON. When you need to know whether your rules are actually being injected, why a hook did not fire, or how tool descriptions are assembled, this beats guessing. codex debug --help on 0.146.0 confirms it, alongside models (model catalogue as JSON) and app-server.

project_root_markers changes where Codex stops looking

By default a directory containing .git is the project root, and that decides where Codex stops searching upward for config and AGENTS.md:

project_root_markers = [".git", ".hg", ".sl"]
# empty list = never search parents; current directory is the root
project_root_markers = []

-c values are parsed as TOML, not JSON

codex --model gpt-6.1-sol                     # prefer the dedicated flag
codex --config model='"gpt-6.1-sol"'          # strings need quotes
codex --config sandbox_workspace_write.network_access=true
codex --config 'shell_environment_policy.include_only=["PATH","HOME"]'

Dot notation for nested keys (mcp_servers.context7.enabled=false). Values that fail to parse as TOML are treated as strings, so quoting changes the result.

Others

--profile is not what most guides say

Older tutorials all describe adding a [profiles.review] table to config.toml. That is no longer the recommended form.

Profiles are now standalone files:

codex -p review    # layers ~/.codex/review.config.toml over the base user config

codex exec --help on 0.146.0 states it directly: Layer $CODEX_HOME/<name>.config.toml on top of the base user config.

The benefit is separation: a review profile is its own file instead of another table in your main config. For review work:

# ~/.codex/review.config.toml
model_reasoning_effort = "high"
approval_policy = "never"
sandbox_mode = "read-only"

never plus read-only deserves a note: reviews stop interrupting you for command approvals while the sandbox still blocks writes. That is not “full permissions” — a distinction many guides blur.

Hooks are powerful, and they fail silently in three ways

Hooks let you run scripts or MCP tools during the agent loop: scan prompts for leaked API keys, run validation when a turn stops, save state before context compaction, generate memory summaries.

Events cover a turn (PreToolUse, PermissionRequest, PostToolUse, PreCompact, PostCompact, UserPromptSubmit, SubagentStop, Stop, Interrupt) plus SessionStart, SessionEnd, and SubagentStart.

Failure one: trust is bound to a hash

Non-managed hooks must be reviewed and trusted via /hooks. The record is bound to the hook’s current hash — edit one line and it becomes a new hook that needs re-trusting.

This is most confusing with project hooks: you keep opening /hooks and it never loads, then start suspecting your own script. More likely the project is untrusted, or the trust gate rejected it.

One issue tested this precisely: a SessionStart hook in the repo root that touches a sentinel file; running codex exec without the flag never creates the sentinel and prints nothing, while adding --dangerously-bypass-hook-trust makes it appear (issue #48715, 0.157.1).

That flag’s own help text is direct: “Intended only for automation that already vets hook sources.” It is for pipelines that audit hooks outside Codex, not for local convenience.

Failure two: hook output gets written to disk

This one is easy to miss. Codex limits model-visible hook output to roughly 2,500 tokens. Beyond that, the full text is written to disk:

<temp_dir>/hook_outputs/<session_id>/<uuid>.txt

The model sees a head-and-tail preview plus the path. The mechanism is called spilling.

So: do not print secrets or sensitive data in hook output — it lands in a temp directory. additionalContextLimit adjusts the threshold (default 2,500; 0 passes the full text to the model and is risky). OpenAI also notes that hook and plugin context accumulates and degrades model performance.

Failure three: background hook output can be discarded

Up to eight background hooks run concurrently per session; the rest queue, and completion order is not guaranteed. When the session ends, unfinished background hooks are cancelled and their output is discarded. SessionEnd always runs synchronously.

If you keep a “write my journal on session end” hook, put it on SessionEnd rather than Stop.

Plugin hooks today

If you are porting a Claude Code-style plugin, note that Agent Plugins 1.0’s extensions.com.openai.hooks field is currently ignored — the loader returns an empty list early (issue #47925, still reproducible on 0.160.0). Only the legacy .codex-plugin/plugin.json form works.

For history: the PR adding user-scripted event hooks was closed unmerged (PR #2904), and the HN thread notes the team does not take external feature PRs — go through issues and RFCs (HN, 2026-01). Hooks shipped eventually, but not as the Claude Code style hook many people assumed.

Subagents: three built-ins, custom files, and two expensive traps

Built-in agents are default (general fallback), worker (execution-focused), and explorer (read-heavy codebase exploration).

The inheritance detail

Custom agents live in ~/.codex/agents/ (personal) or .codex/agents/ (project), one file per agent, and require three fields: name, description, and developer_instructions.

The easy-to-miss part is reasoning effort resolution. If a custom file sets model but not model_reasoning_effort, it keeps the previously resolved effort — from an explicit spawn, then the [agents] default, then the parent session. Set both if you want a specific effort.

Other session settings — sandbox_mode, mcp_servers, skills.config — inherit from the parent when omitted.

Also: a custom agent name that collides with a built-in wins. You can define your own explorer and replace the built-in one.

Global [agents] keys include enabled (default true), max_concurrent_threads_per_session, default_subagent_model, default_subagent_reasoning_effort, and interrupt_message. That last one defaults to true; setting it false keeps the model-visible interruption message out of the agent’s context.

Trap one: your subagent does not start on your model

Users report that a forked worker actually runs on the orchestrator’s default model for the first ~40 turns before switching to default_subagent_model. The switch comes too late and the orchestrator’s token share balloons (issue #45790, 0.154.0).

If you design multi-agent work and watch the bill, run a cheap model as the orchestrator for “investigate and delegate” and reserve the strong model for the work itself.

Trap two: runaway subagents cost real money

One user hit a simple UI task in VS Code on 0.144.0-alpha.4 where Codex automatically opened 826 child tasks (~2.15T tokens) and billed $79,664 (HN, 2026-09). It is not isolated: there are several reports around the same period of local rollouts recording 36k tokens while the dashboard showed a few million consumed (issue #48919).

Reasonable safeguards: set a hard spend cap at the OpenAI billing layer (not the Codex layer); cap concurrency; use codex exec rather than the interactive session in CI.

One more limitation

codex exec --ephemeral and custom subagents are incompatible — ephemeral runs do not persist a thread, so custom agents fail with no thread with id (issue #41474). Subagents also cannot override shell_environment_policy.set, so you cannot hand each one a different API key (issue #46457).

codex exec is the part worth automating

Interactive codex is for people. For scripts and CI, use codex exec. Confirmed on 0.146.0:

FlagWhat it does
--jsonStreams events to stdout as JSONL
--output-schema <FILE>Constrains the final response with a JSON Schema
-o, --output-last-message <FILE>Writes only the final message to a file
--ephemeralSkips writing session files
--ignore-user-configSkips $CODEX_HOME/config.toml
--ignore-rulesSkips execpolicy .rules files
--skip-git-repo-checkAllows running outside a Git repository
--dangerously-bypass-hook-trustSkips hook trust for externally audited automation

--output-schema is the most underrated one — it is how you get reliable structured output from automation instead of parsing prose.

The framing has also shifted. As one long-running user put it: “the hardest problem doesn’t seem to be coding anymore. It’s orchestration, approvals, and knowing when an agent should hand control back to a human” (HN, 2026-06).

One parsing trap in the JSON output

web_search items carry two id members — first the exec item ID, then the search call ID:

{"type":"item.started","item":{"id":"item_5","type":"web_search","id":"ws_0ffce7c01785eab1016abe8f507bc087d297e8f248d394573e","query":"","action":{"type":"other"}}}

Duplicate member names are permitted by RFC 8259 but handling is unspecified, and strict decoders reject the line — Go’s encoding/json/v2 refuses duplicates by default (issue #50053, 0.159.0; another project hit the same shape on 0.156.1). If you write a CI parser, do not assume item.id is unique.

--ignore-user-config does more than its help text says

The help text says it does not load $CODEX_HOME/config.toml. In practice it also skips project-level .codex/hooks.json and .codex/config.toml including its MCP servers — while still writing trust entries (issue #49333, 0.157.1).

For a clean CI baseline, point CODEX_HOME at a temporary directory instead of relying on this flag.

Platform gotchas worth knowing

Community-reported issues rather than confirmed behaviour changes. Check your own version.

macOS — running subagent thread transitions in a project under ~/Documents can fail with invalid cwd: Operation not permitted (os error 1), because the macOS Documents TCC permission affects cwd validation. It shows up often in the PyCharm integrated terminal (issue #37229, 0.146.1).

Linux — --yolo still runs with NoNewPrivs: 1 in /proc/self/status, so a NOPASSWD sudo rule cannot escalate (issue #48908, 0.157.1). Yolo does not get you root.

Linux + Docker — Codex parses /proc/self/mountinfo, and a Docker bridge network’s net:[inode] entry trips path validation with “mountinfo path is not absolute”, which takes down the whole filesystem sandbox and with it every apply_patch (issue #47987).

Windows — the .sandbox-bin lockout has a durable fix. The symptom is recurring helper_sandbox_lock_failed. The cause is a protected DACL that grants SYSTEM/Admins Modify but not WRITE_DAC, combined with the setup marker being deleted before provisioning finishes (issue #51468, issue #50520).

The community-verified durable fix changes the owner rather than the permissions:

takeown /F "%USERPROFILE%\.codex\.sandbox-bin" /A

Do not add /R. Ownership change lets LocalSystem reach WRITE_DAC implicitly while leaving the DACL alone. Several users report surviving 4–5 reprovision cycles plus a restart; icacls /grant SYSTEM:F only recovers once.

There is also a Windows proxy trap: with a Proxifier-style tool, Codex App needs two rules — localhost;127.0.0.1 forced Direct, everything else through the proxy — or local calls loop back through the proxy (V2EX report).

apply_patch — the syntax accepts duplicate paths and reversed hunks, but the runtime rejects them. One 30-day local count: 83 failures from duplicate paths, 1,607 from missing source lines (issue #50549).

Two habits worth forming

Most of this collapses into one idea: Codex problems are rarely model problems. They are harness behaviours that fail quietly — config dropped without warning, AGENTS.md truncated without warning, hooks rejected without warning, --ignore-user-config reaching further than documented.

So two habits cover most of it:

  1. Run --strict-config after every upgrade and remove what it flags.
  2. Check the mechanism before blaming the model. codex doctor for state, codex debug prompt-input for what the model actually receives, codex features list for which flags are stable in your version.

One last thing worth a look: run codex features list on your own machine. On 0.146.0, hooks, goals, multi_agent, shell_snapshot, browser_use, code_mode_host, and remote_plugin are all stable and enabled by default, while memories is stable but off. Knowing what is already on is more useful than knowing what exists.

Most of what saves time here is moving instructions out of your prompts and into files the agent already reads — the reusable AI prompt templates cover the other half of that job. For more tested tools in this vein, browse Save Effort.


Sources

Official documentation

The official docs domain developers.openai.com redirects to learn.chatgpt.com.

Community reports

AGENTS.md #7138 — silently truncated · #8759 — global location not read by default · #12115 — nested AGENTS.md · #41704 — chains no longer respected · #44498 — injected instructions pollute search

Configuration and strict mode #33181 — strict config rejects managed project entries · #51481 — remote stdio MCP fails strict validation · #49333 — —ignore-user-config also skips the project layer · #51116 — bypass flag overridden by managed profile

Hooks PR #2904 — user-defined event hooks · HN — team does not take external feature PRs · #47925 — Agent Plugins 1.0 hooks never loaded · #48715 — project hooks silently not executed

Subagents and usage #45790 — subagent model switch is late · #40299 — subagents managed unreliably · #41474 — —ephemeral breaks custom subagents · #46457 — subagent env policy · #48919 — usage accounting mismatch · HN — $78,000 runaway subagents · HN — file-based sub-agents

exec and JSON output #50053 — web_search items carry id twice · #50549 — apply_patch path constraints

Platform #37229 — macOS Documents + subagents EPERM · #41138 — PowerShell shell_snapshot returns None · #36435 — zsh KSH_OPTION_PRINT · #48908 — —yolo still enforces NoNewPrivs · #47987 — Docker nsfs breaks sandbox · #50520 and #51468 — Windows .sandbox-bin lockout · V2EX — Windows Proxifier rules

Engineering practice HN — precision with PLANS.md and checksums · HN — production monolith workflow · HN — Codex CLI is going native · HN — memory footprint data point