The PR works. Tests pass, types check. But every reviewer leaves the same kind of comment: “use async/await, not .then()”, “early return instead of nested if”, “params as an object”, “missing JSDoc”. The code looks like it was written by someone unfamiliar with the codebase, because it was. Codex’s neutral default style is fine on a greenfield repo; on a 100K-line codebase with established patterns, it reads as an outsider’s voice.
Fastest fix (as of June 2026): add a “Canonical style examples” table to AGENTS.md that points each style dimension at one real file in your repo (async/await -> src/services/auth.ts, etc.), then name that file in the prompt: “Match the style of src/services/auth.ts.” Codex reads AGENTS.md before it touches any code, so a concrete file pointer beats any abstract “match existing style” rule. Everything that can be linted (then-vs-await, import order, quotes, naming) goes to ESLint + Prettier so Codex’s formatting cannot drift. Those two moves remove roughly 70% of style nits.
The root cause: style mismatch is Codex defaulting to a generic style because your codebase’s specific style was never written down anywhere it reads. Two levers fix it: anchor to a canonical file Codex must copy, and enforce trivial style via lint so Codex’s defaults can’t reach a PR.
How AGENTS.md is loaded (Codex CLI, June 2026): Codex reads ~/.codex/AGENTS.md (global) first, then walks from the Git root down to your current directory, concatenating one AGENTS.md per folder. Files closer to your working directory appear later and override earlier guidance, so a per-package AGENTS.md can set stricter style than the repo root. Combined instructions are capped at 32 KiB by default (project_doc_max_bytes in ~/.codex/config.toml); past that, Codex truncates, so keep the style table tight.
Common causes
Ordered by hit rate, highest first.
1. No style examples in AGENTS.md
AGENTS.md says “match the existing style” — which style? Codex picked one. Without naming a canonical example, the rule is too vague to follow.
How to spot it: grep -i "style\|canonical\|example" AGENTS.md returns abstract rules but no file pointers.
2. Stylistic lint rules missing or disabled
You believe in early returns; ESLint has no rule enforcing it. You believe in object params; no rule enforcing that. The “style” exists only in heads, so Codex’s output drifts.
How to spot it: Run pnpm eslint on Codex’s output. If it passes but the code still feels wrong, the convention isn’t lintable as configured.
3. Codex blended styles from multiple retrieved files
It read three files: one using async/await, one with .then(), one with raw callbacks. Without canonical guidance, Codex picked one or — worse — produced a hybrid.
How to spot it: New code mixes patterns inconsistently within itself. Hybrid output is a tell.
4. No prettier / formatter to mop up trivial differences
Indent width, quote style, trailing commas. Without Prettier-on-CI, every commit accumulates micro-drift. Codex’s output adds to the pile.
How to spot it: pnpm prettier --check . produces a long list of unformatted files. Style isn’t enforced; it’s voluntary.
5. Codex used a popular pattern over yours
The codebase uses const Component = () => {} (arrow); Codex generated function Component() {} (declaration). Both work; the team-specific pattern wasn’t named.
How to spot it: Grep your codebase for both forms — the dominant one is the canonical. Codex picked the minority pattern.
6. JSDoc / comments differ in tone or completeness
Your codebase has detailed @param docs with examples. Codex writes brief comments. Or vice versa — Codex writes verbose blocks for trivial functions where your style is sparse.
How to spot it: Pick a recently-merged PR by a team lead; compare comment density to Codex’s. Mismatch in density or tone is visible immediately.
Shortest path to fix
Ordered by ROI. Steps 1 and 2 together remove 70% of style drift.
Step 1: Pick one canonical file per stylistic dimension
Identify the most exemplary file your team has produced for each style concern, and document it in AGENTS.md:
## Canonical style examples
| Dimension | Canonical file |
|---|---|
| Async / Promise | src/services/auth.ts (async/await only) |
| Error handling | src/lib/errors.ts (typed AppError extension) |
| React component | src/components/UserCard.tsx (arrow function, props destructured) |
| API route | src/app/api/users/route.ts (NextResponse, zod validation) |
| Repository pattern | src/db/repositories/user.repository.ts |
| Test layout | src/services/auth.test.ts |
Match these files when writing new code in their area.
Codex reads this and now has concrete targets, not abstract rules.
Step 2: Point at the canonical in every code prompt
Add a `findOrgById` lookup.
Match the style of `src/services/auth.ts`:
- async/await (no .then())
- Early returns, no nested if/else
- Object params if 2+ args
- Typed `AppError` on missing/invalid
Generate the file + its test (matching `src/services/auth.test.ts`).
Step 3: Enforce trivial style with lint + format
For every style rule that can be linted, lint it. Add to ESLint config:
// .eslintrc.cjs (excerpt)
module.exports = {
rules: {
"prefer-arrow-callback": "error",
// No core "no-then" rule exists. Ban .then() with an AST selector:
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[property.name='then']",
message: "Use async/await, not .then().",
},
],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/naming-convention": [
"error",
{ selector: "variable", format: ["camelCase", "UPPER_CASE"] },
{ selector: "function", format: ["camelCase"] },
{ selector: "typeLike", format: ["PascalCase"] },
],
"import/order": ["error", { groups: ["builtin", "external", "internal"] }],
},
};
Note: "no-then" is not a built-in ESLint rule (it ships only via a third-party plugin), which is why the selector-based no-restricted-syntax above is the dependency-free way to ban .then(). If your enforce-early-return wish needs a rule too, unicorn/no-negated-condition and no-else-return cover most of it.
Add Prettier with explicit config (.prettierrc.json):
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}
Now pnpm lint && pnpm format mops up everything Codex got slightly wrong.
Step 4: Require lint to be part of “done”
In the prompt:
After writing, run:
1. `pnpm prettier --write <files>`
2. `pnpm eslint <files> --max-warnings 0`
Both must succeed before you say done. Paste the exit codes.
This automates the cleanup pass.
Step 5: Reject style-drift output, re-prompt with canonical
If Codex still produces drifted output:
Your output uses `.then()` and `function Foo() { }`.
This codebase uses async/await + arrow components — see `src/services/auth.ts` and `src/components/UserCard.tsx`.
Re-write matching those files. Run `pnpm eslint` before saying done.
Session-level correction sticks for the rest of the run.
Step 6: Audit older files; consolidate competing styles
If your repo genuinely has two competing styles, declare a winner and migrate, or Codex will keep picking randomly:
# Count each pattern
grep -rc "\.then(" src/ | awk -F: '{s+=$2} END {print "then:", s}'
grep -rc "await " src/ | awk -F: '{s+=$2} END {print "await:", s}'
The minority pattern goes on the “migrate” list; Codex doesn’t try to extend it.
How to confirm it’s fixed
Run the same gate a reviewer would, on Codex’s diff only:
# 1. Format + lint Codex's changed files (stage them first)
pnpm prettier --write $(git diff --name-only --cached)
pnpm eslint $(git diff --name-only --cached) --max-warnings 0
# 2. Spot-check against the canonical
git diff --cached src/services/newFeature.ts # should read like auth.ts
You’re fixed when: eslint --max-warnings 0 exits 0, prettier --check reports no changes, and a side-by-side git diff against the canonical file shows the same async style, import order, and comment density. If a human reviewer still flags style, the rule they flagged is either not in your lint config or not in the AGENTS.md table — add it there, not in a one-off comment.
Prevention
- One canonical file per stylistic dimension, listed in
AGENTS.mdwith a clear table - Lint enforces every style rule that can be enforced; humans and Codex never argue about formatting
- Prettier runs in pre-commit (husky + lint-staged) so format drift can’t reach a PR
- Every code prompt names the canonical file Codex must match
- Resolve in-repo style inconsistency by declaring a winner; Codex extends whatever it sees first
- Generated code (Prisma, codegen, etc.) is exempted in
.eslintignoreso lint focuses on hand-written and Codex code - Keep the
AGENTS.mdstyle table short: it shares a 32 KiB instruction budget with everything else Codex loads
FAQ
Codex passes lint but still gets style comments. Why?
The comment is about a convention that isn’t lintable as configured, so it lives only in reviewers’ heads. Two fixes: write the rule into the AGENTS.md canonical table so Codex copies a real example, and, if the rule has an AST shape (then-vs-await, named vs default export), add a no-restricted-syntax selector so it becomes enforceable. Anything you can’t lint, you anchor to a file.
Where exactly does Codex read style rules?
Codex CLI loads ~/.codex/AGENTS.md (global), then every AGENTS.md from the Git root down to your working directory, with closer files overriding. There’s no separate “style config” file. Put the canonical table in the repo-root AGENTS.md, or in a package-level AGENTS.md if one package needs stricter rules. An AGENTS.override.md in a folder replaces the normal file for that folder.
Should the rule go in AGENTS.md or in the per-task prompt?
Both. AGENTS.md is durable guidance Codex reads every session; the prompt is where you point at the specific canonical file for this change (“match src/services/auth.ts”). Durable rules drift less when they’re also repeated in the prompt that matters.
My repo genuinely has two competing styles. What do I tell Codex?
Pick a winner and say so explicitly, because Codex extends whatever pattern it retrieves first. Count both with grep -rc, put the minority pattern on a migrate list, and exclude those files from the canonical examples so Codex never copies the losing style.
Does this work for the Codex cloud agent and IDE extension too, or only the CLI?
The AGENTS.md format is shared across Codex surfaces (CLI, cloud, IDE), so the canonical-table approach works everywhere. The exact discovery order and the 32 KiB cap described here are the Codex CLI defaults; the cloud and IDE read the same repo AGENTS.md but manage their own context budget.
Related
- Codex misses project-specific conventions
- Codex ignores project structure
- Codex output does not fit the project
- Codex beginner guide
- Codex code review workflow
- Codex vs Claude Code
- Custom instructions with AGENTS.md (OpenAI Codex docs)
no-restricted-syntax(ESLint docs)
Tags: #Codex #Coding agent #Troubleshooting #Debug #Style mismatch