Codex-Generated Code Does Not Fit the Existing Style

Functionally correct but reads like a stranger wrote it — async/await mixed with .then(), wrong import ordering, comment style off. Anchor to a canonical file + enforce with lint.

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” style is fine on a greenfield repo; on a 100K-line codebase with established patterns, it reads as an outsider’s voice.

Style mismatch comes from Codex defaulting to a generic style when your codebase uses something specific. Two levers fix it: anchor to a canonical file Codex must copy from, and enforce trivial style via lint so Codex’s defaults can’t drift the formatting.

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.

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-then": "error",                          // no .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"] }],
  },
};

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.

Prevention

  • One canonical file per stylistic dimension, listed in AGENTS.md with a clear table
  • Lint enforces every style rule that can be enforced; humans/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 .eslintignore so lint focuses on hand-written/Codex code

Tags: #Codex #Coding agent #Troubleshooting #Debug #Style mismatch