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:
-
--strict-configrejects 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. -
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 needenvironment_idinstead (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.enabled | features.network_proxy | Actual behaviour |
|---|---|---|
| false | either | Commands cannot reach the network at all |
| true | false | Commands connect directly — the domain rules are not enforced |
| true | true | Commands 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
[history] persistence = "none"disables local session transcripts;history.max_bytescaps the file and drops the oldest entries.hide_agent_reasoning = truesuppresses reasoning output, which keeps CI logs readable.tui.alternate_screen = "never"keeps your terminal scrollback.tool_output_token_limitsets the token budget for a single tool result in history — useful when debugging in a large repository.file_openeracceptscursor,windsurf, orvscode-insiders, turningmain.py:42into a clickable link.
--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:
| Flag | What it does |
|---|---|
--json | Streams 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 |
--ephemeral | Skips writing session files |
--ignore-user-config | Skips $CODEX_HOME/config.toml |
--ignore-rules | Skips execpolicy .rules files |
--skip-git-repo-check | Allows running outside a Git repository |
--dangerously-bypass-hook-trust | Skips 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:
- Run
--strict-configafter every upgrade and remove what it flags. - Check the mechanism before blaming the model.
codex doctorfor state,codex debug prompt-inputfor what the model actually receives,codex features listfor 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
- Config basics — configuration precedence, project trust
- Advanced Configuration — forbidden project keys,
project_root_markers,notify, history, TUI - Configuration Reference — key semantics and defaults
- Hooks — lifecycle events, trust model, spilling, concurrency
- Subagents — custom agent schema and inheritance
- Permissions — permission profiles, the network three-switch matrix
- Build skills — local skill loading and enabling
- openai/codex README — installation and login
The official docs domain
developers.openai.comredirects tolearn.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