OpenAI Codex Configuration: Project-Level Instructions
Why your Codex setup keeps producing inconsistent code
You open a terminal, run codex, and ask it to add a feature. It writes code that technically works but ignores your linting rules, imports the wrong HTTP client, and formats error handling in a style nobody on the team uses. Ask a teammate to run the exact same prompt in their own clone of the repo, and Codex produces something different again — different test framework assumptions, different naming conventions, different levels of caution around database migrations.
This isn't a model quality problem. It's a configuration problem. Codex CLI ships with sensible defaults, but defaults are, by definition, generic. The moment you have a real codebase with real conventions — a specific package manager, a monorepo layout, internal libraries the model has never seen, a "never touch this directory" rule — you need to tell Codex about your project explicitly. Codex gives you two complementary mechanisms to do this: AGENTS.md files for natural-language project instructions, and config.toml for structural, machine-enforced settings like sandbox mode, approval policy, and model parameters.
Most people discover one of these and stop. They drop a single AGENTS.md at the repo root and call it done, or they tweak ~/.codex/config.toml globally and wonder why project-specific rules don't stick across different repos. This article walks through both systems properly — how Codex discovers and merges instructions, which config keys actually matter at the project level, and how to structure a setup that survives contact with a real team and a real monorepo.
How Codex discovers project instructions
Codex doesn't read a single instructions file. It builds a layered instruction chain every time you start a session, and understanding that chain is the difference between "my rules are being followed" and "my rules are silently ignored."
The discovery process works in two scopes:
- Global scope: Codex checks your Codex home directory (
~/.codexby default) forAGENTS.override.mdfirst. If that doesn't exist, it falls back to~/.codex/AGENTS.md. Only one file loads at this level — whichever is found first, non-empty. - Project scope: Starting from your Git repository root (or the current directory if there's no
.git), Codex walks downward toward your current working directory. At every directory along that path, it looks forAGENTS.override.md, thenAGENTS.md, then any names listed inproject_doc_fallback_filenames. It includes at most one file per directory level.
Once collected, the files are concatenated from the root downward, separated by blank lines. This ordering matters: instructions closer to your current working directory appear later in the merged prompt, which means they effectively override or refine guidance from higher-level files. A rule in packages/api/AGENTS.md can narrow or contradict something stated in the repo-root AGENTS.md, and Codex will treat the more specific, closer file as the more authoritative one for that session.
A few behavioral details that trip people up:
- Empty files are skipped silently. If you create an
AGENTS.mdwith nothing in it (say, as a placeholder), Codex moves on without complaint — it won't break the chain, but it also won't help you. - There's a byte cap. The
project_doc_max_bytessetting (32 KiB by default) limits how much total instruction text gets pulled in. If your combined AGENTS.md files exceed that, content gets truncated. This is a common silent failure — teams write increasingly long AGENTS.md files over months and eventually the most important rules (often at the bottom) get cut off. - Discovery doesn't look below your working directory. If you
cdinto a subfolder and run Codex from there, it won't pick up anAGENTS.mdsitting in a sibling or child folder outside that path. Run Codex from the directory level where the relevant instructions actually live. - The chain rebuilds every run. There's no stale cache to clear — if you edit
AGENTS.md, the nextcodexinvocation picks it up immediately.
Writing an effective root-level AGENTS.md
The repo-root AGENTS.md is your project's constitution — the rules that apply everywhere unless a more specific file overrides them. Keep it focused on things that are true for the whole codebase: tooling, architecture boundaries, and non-negotiables.
Here's a realistic example for a TypeScript monorepo:
# AGENTS.md
## Project overview
This is a pnpm monorepo with three workspaces: `apps/web` (Next.js),
`apps/api` (Express + Prisma), and `packages/shared` (types and utils
consumed by both). Node 20+. Package manager is pnpm — never use npm
or yarn commands.
## Build, lint, and test
- Install: `pnpm install`
- Run all tests: `pnpm test`
- Run a single workspace's tests: `pnpm --filter api test`
- Lint before considering any change complete: `pnpm lint`
- Type-check: `pnpm typecheck`
## Conventions
- Use named exports only. No default exports, including for React
components.
- API routes live in `apps/api/src/routes/` and must have a matching
Zod schema in `apps/api/src/schemas/`.
- Never write raw SQL. All database access goes through Prisma models
in `packages/shared/prisma`.
- Error handling: throw `AppError` subclasses from
`packages/shared/errors.ts`, never raw `Error` or string throws.
## Things to never do
- Never modify files under `infra/terraform/` without being asked
explicitly — these changes require a human review.
- Never commit `.env` files or print secrets in logs.
- Do not add new npm dependencies without explaining why in your
summary — prefer what's already in `package.json`.
## Testing expectations
Every new API route needs a corresponding integration test in
`apps/api/test/routes/`. Every new shared util needs a unit test in
`packages/shared/test/`. Run the full suite before declaring a task
finished.Notice what this file does *not* do: it doesn't try to teach Codex your entire architecture in exhaustive prose. It states facts (package manager, workspace layout), gives exact commands (so Codex doesn't guess at npm test when it should run pnpm --filter api test), and calls out hard boundaries. Vague guidance like "write clean code" or "follow best practices" wastes bytes in your 32 KiB budget without changing model behavior — be concrete and imperative instead.
Scoping instructions to subdirectories
The real power of the discovery hierarchy shows up when your project has parts with genuinely different rules. A monorepo with a legacy PHP service next to a new Rust microservice needs different instructions in each, and forcing everything into one root file makes it noisy and easy to misapply.
Say you have apps/api/AGENTS.md:
# AGENTS.md — API service
This extends the repo-root AGENTS.md. Rules here are specific to
`apps/api`.
## Framework specifics
This service uses Express 5 with async route handlers. Every route
handler must be wrapped in the `asyncHandler` utility from
`src/middleware/asyncHandler.ts` — do not use raw try/catch inside
route handlers.
## Database migrations
Migrations are generated with `pnpm --filter api prisma migrate dev`.
Never hand-edit files under `prisma/migrations/`. If a migration is
needed, generate it and show the generated SQL in your summary before
considering the task done.
## Auth
All protected routes must call `requireAuth()` from
`src/middleware/auth.ts` as the first middleware. Session tokens are
JWTs verified against `process.env.JWT_SECRET` — never hardcode a
secret or bypass this check "for testing."Because Codex is being run from inside apps/api/, this file's content gets appended *after* the repo-root file in the merged prompt — meaning if there's ever a conflict (unlikely, but possible), the more specific API-level rule wins for that session. This is exactly the behavior you want: general rules apply everywhere, specific rules refine behavior in the directories that need it, and you don't have to repeat the pnpm/lint/test boilerplate in every subfolder.
For a package that's rarely touched or has stricter rules — like a payments module — you can go a level deeper still, e.g. apps/api/src/payments/AGENTS.md, with instructions like "never change the rounding logic in calculateFee() without an explicit request" or "all changes here require a corresponding entry in CHANGELOG-payments.md."
AGENTS.override.md for local exceptions
Sometimes you need to override project instructions on your own machine without touching a file that's checked into version control — for example, if you're debugging something and temporarily want Codex to be more permissive, or you disagree with a team convention and want your local sessions to behave differently while a discussion is ongoing.
AGENTS.override.md exists for exactly this. At any directory level, if it's present, it's used *instead of* AGENTS.md at that level — not in addition to it. This makes it a good fit for a .gitignore'd, developer-local file:
apps/api/AGENTS.override.md (gitignored, local only)# Local override — apps/api
Temporarily allow hand-editing prisma/migrations while I debug a
seed-data issue. Ignore the "never hand-edit migrations" rule from
AGENTS.md for this session only.Because override files are per-directory and take full precedence over the regular file at that same level (they don't merge with it — one or the other loads, not both), keep them short and remember they completely replace the committed guidance for that directory, not just add to it.
Configuring project-level config.toml
AGENTS.md handles natural-language guidance the model reads and reasons about. config.toml handles structural settings that Codex enforces mechanically — sandboxing, approval prompts, model selection, and byte limits. Codex reads configuration from ~/.codex/config.toml for personal defaults, and layers project-level overrides from a .codex/config.toml file inside your repository.
A typical project .codex/config.toml looks like this:
# .codex/config.toml — checked into the repo
# How much of each AGENTS.md file to read
project_doc_max_bytes = 49152
# Try these filenames if AGENTS.md isn't present in a directory
project_doc_fallback_filenames = ["CONTRIBUTING.md", "TEAM_GUIDE.md"]
# Default sandbox: allow writes inside the workspace, block network
# and anything outside the repo
sandbox_mode = "workspace-write"
# Ask before running commands Codex hasn't been explicitly told about
approval_policy = "on-request"
[sandbox_workspace_write]
# Explicitly allow writes to these paths beyond the default workspace root
writable_roots = ["./tmp", "./.cache"]
network_access = falseProject config files are ordered from the project root down to your working directory — closest wins — but only for projects marked as trusted. There's an important security boundary here: a .codex/config.toml inside a repo you haven't explicitly trusted cannot silently change things like credential handling, provider authentication, or which profile gets selected. That's intentional. A malicious or compromised repo shouldn't be able to reconfigure your Codex credentials just because you ran codex inside it. Mark a project trusted once you've reviewed it:
# ~/.codex/config.toml (your personal, global file)
[projects."/Users/pramod/code/teachyou-platform"]
trust_level = "trusted"Profiles for different modes of work
A single project often needs more than one operating mode. You might want a fast, low-friction profile for exploratory work and a stricter, higher-reasoning profile for anything touching production infrastructure. Codex profiles let you define named configuration layers in ~/.codex/config.toml and switch between them with a flag.
# ~/.codex/config.toml
model = "gpt-5.5"
model_reasoning_effort = "medium"
approval_policy = "on-request"
[profiles.fast]
model_reasoning_effort = "low"
approval_policy = "never"
sandbox_mode = "workspace-write"
[profiles.infra]
model_reasoning_effort = "xhigh"
approval_policy = "untrusted"
sandbox_mode = "read-only"Note the structural detail that catches people out: keys inside a profile section are written flat, not nested under another [profiles.x.y] table. model_reasoning_effort = "xhigh" sits directly under [profiles.infra].
Invoke a profile from the command line:
codex --profile fast "add a loading spinner to the dashboard"
codex --profile infra "review this terraform plan, don't apply anything"The fast profile is useful for UI tweaks and scaffolding where a wrong guess is cheap to fix. The infra profile forces read-only sandboxing and the highest reasoning effort for anything you'd hand to a junior engineer only with close supervision — Codex can look at files and reason carefully, but it cannot write or execute without your explicit approval on each step.
Sandbox mode and approval policy, explained properly
These two settings are the ones people configure carelessly and regret later, so it's worth being precise about what each value actually does.
sandbox_mode controls what Codex is *allowed* to touch:
read-only— Codex can read files and run read-only commands, but cannot write or execute commands with side effects without you stepping in.workspace-write— Codex can write files and run commands, but confined to the project workspace (plus anywritable_rootsyou add). Network access is off by default.danger-full-access— no sandboxing. Use this only in disposable containers or CI runners you don't mind Codex having full control over, never on your primary machine.
approval_policy controls when Codex *asks* before acting:
untrusted— approve nearly everything, appropriate for a repo you don't fully trust yet or a profile likeinfraabove.on-request— Codex proceeds with routine actions but asks before anything it flags as risky (deleting files, running migrations, network calls).never— full autonomy, no prompts. Pair this only with a tightsandbox_mode, never withdanger-full-access, unless you're intentionally running Codex unattended in an isolated environment.
A sane default for day-to-day feature work on your own machine is workspace-write plus on-request. Reserve never + workspace-write for the fast profile above, and reserve anything looser than that for containers, not your laptop.
A layered setup that actually holds up
Putting it together, a well-configured repo looks like this:
your-repo/
├── AGENTS.md # repo-wide conventions, checked in
├── .codex/
│ └── config.toml # project sandbox/approval defaults, checked in
├── apps/
│ ├── web/
│ │ └── AGENTS.md # frontend-specific rules
│ └── api/
│ ├── AGENTS.md # backend-specific rules
│ └── AGENTS.override.md # gitignored, personal local override
└── packages/
└── shared/
└── AGENTS.md # shared-lib rules (versioning, breaking changes)Checked-in files (AGENTS.md, .codex/config.toml) encode team-wide agreement — every engineer and every CI-triggered Codex run inherits the same behavior. AGENTS.override.md files stay local and gitignored for personal exceptions. Global settings in ~/.codex/config.toml — your API defaults, personal profiles, model preference — stay out of the repo entirely, so they never leak into a teammate's environment or a CI job.
The habit that makes this durable isn't a one-time setup — it's revisiting AGENTS.md the same way you'd revisit a style guide. When Codex makes a mistake that a written rule would have prevented, add the rule. When a rule stops being true (you migrated off a library, changed a folder structure), delete it. Stale instructions are worse than no instructions, because they actively point the model in the wrong direction while looking authoritative.
Common mistakes to avoid
A few patterns show up repeatedly in teams adopting Codex at the project level:
- One giant root AGENTS.md for a multi-service monorepo. This blows past
project_doc_max_bytesfaster than expected and mixes unrelated concerns. Split by directory instead. - Treating AGENTS.md as documentation instead of instructions. A README explains what the code does for humans; AGENTS.md should tell the model what to do and what not to do. Reusing your README verbatim usually wastes the byte budget on context the model doesn't need to act correctly.
- Setting `approval_policy = "never"` with `sandbox_mode = "danger-full-access"` on a laptop. This combination removes every safety net at once. Keep at least one of the two constrained.
- Forgetting that project `.codex/config.toml` can't override trust-sensitive keys. If you're trying to change credential or provider settings from a project file and it's not taking effect, that's by design — those keys only come from your global, trusted configuration.
- Not marking the project trusted, then wondering why `.codex/config.toml` seems to be ignored. Untrusted projects skip project-scoped
.codex/layers entirely.
Where to go from here
Project-level configuration is what turns Codex from a generic coding assistant into something that behaves like it actually works on your team — same lint rules, same architectural boundaries, same caution around the parts of the codebase that deserve it. The two systems complement each other: AGENTS.md shapes how the model reasons about your code, and config.toml enforces the hard limits around what it's allowed to do while reasoning.
Start small: a focused root AGENTS.md with real commands and real boundaries, a .codex/config.toml with a sensible workspace-write + on-request default, and one or two profiles for the modes of work you actually do. Expand into subdirectory-level files only once you feel the root file getting crowded or contradictory.
If you want a structured, hands-on walkthrough of setting this up end to end — including sandbox tuning, MCP integration, and multi-repo profile strategies — our OpenAI Codex CLI Tutorial course on teachyou.ai covers exactly this, from a first codex run through production-grade team configuration.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
CodexLearn to drive OpenAI's coding agent: real tasks, safe sandboxing, and terminal-to-cloud workflows that ship.