Codex Agent Output Conflicts With Prettier

Codex writes a file, Prettier reformats it on save, and the diff explodes. Fix it by running the project's pinned Prettier inside the agent loop and aligning AGENTS.md style hints to .prettierrc.

Codex writes a clean-looking patch. You open the file in your editor and Prettier-on-save rewrites every line: quotes flip, trailing commas appear, semicolons get added or stripped, line breaks rearrange. Now the git diff is twice as long as the actual change. Worse, when the agent tries to follow up on its own edit later, it cannot match its original lines anymore. Pre-commit hooks fail. CI lint flags the file. The “ten lines changed” turns into 300.

Fastest fix: add one rule to your AGENTS.md so Codex formats with the project’s pinned Prettier before it ever reports “done”:

Run `pnpm exec prettier --write` on every file you touch.
Then `pnpm exec prettier --check` must return 0 before you finish.

That single handshake removes the editor as a hidden second formatter. The rest of this page covers why your draft still drifts (version mismatch, AGENTS.md vs .prettierrc disagreement, ESLint fighting Prettier) and how to make the agent’s first draft already match what Prettier will produce.

This is not a Codex bug; it is a missing handshake. The agent does not know your .prettierrc exists, or it knows but the prompt did not load it, or it ran format but with default settings instead of project settings. The fix is to make Prettier part of the agent’s “done” definition, and to feed the relevant rules into the agent’s style hints so its first draft already matches what Prettier will produce.

Which bucket are you in

SymptomMost likely causeJump to
Diff is 5x bigger than the agent reported; prettier --check failsAgent never formatted; editor did on saveCause 1, Step 1
Uniform changes (all ' to ", semicolons everywhere)Agent ran a different Prettier versionCause 2, Step 3
Agent’s draft is consistently “wrong” before any saveAGENTS.md style disagrees with .prettierrcCause 3, Step 2
File flips back and forth depending on which tool runs lastESLint and Prettier both formattingCause 4, Step 4
Stray non-ASCII (smart quotes, nbsp) only in agent-written linesAgent emitted invisible charactersCause 5, Step 5
A hand-aligned block keeps getting nudgedEdit landed inside a // prettier-ignore zoneCause 6, Step 6

Common causes

Ordered by hit rate.

1. Agent never ran Prettier; editor did

The agent finished, saved the file, declared “done”. On your machine, editor-on-save Prettier reformatted the file. The clean-looking output gets transformed at the last second.

How to spot it: git diff looks much larger than the agent reported. Run prettier --check <file> — many lines fail.

2. Agent ran a different Prettier than the project’s

pnpm prettier --write vs npx prettier@3.6 --write vs a globally-installed prettier produce different output. Prettier ships breaking-ish formatting tweaks across minor versions (3.5 added the objectWrap and experimentalOperatorPosition options, 3.6 shipped an experimental fast CLI, and the line stands at 3.8.x as of June 2026), so a global prettier that resolves to 3.8 will not match a project pinned to 3.3.

How to spot it: pnpm exec prettier --version inside the project differs from whatever the agent ran. The diff is uniform formatting noise (for example every ' becomes ", or trailing commas appear on every multiline call).

3. Style hints in AGENTS.md disagree with .prettierrc

AGENTS.md says “use single quotes” but .prettierrc has "singleQuote": false. Agent obeys AGENTS.md; Prettier wins on save. They are fighting.

How to spot it: Manually diff AGENTS.md style claims against .prettierrc / prettier.config.js. Any disagreement = noisy diffs forever.

4. Mixed Prettier and ESLint formatters

ESLint with eslint-plugin-prettier AND a standalone Prettier on save produce slightly different orderings of arrow parens, trailing commas in JSX. Agent picked one, the chain of tools picked the other.

How to spot it: Files reformat differently depending on which tool you run last. Run pnpm prettier --check . && pnpm eslint . — if both pass after prettier --write but eslint --fix then changes lines back, you have a conflict.

5. Agent inserted invisible characters Prettier reformats

Smart quotes, non-breaking spaces, en-dashes — Codex sometimes inserts these in comments or strings. Prettier (or some plugin) normalizes them.

How to spot it: cat -A <file> | grep -E '\xc2\xa0|\xe2\x80' finds non-ASCII. Compare to the agent’s output text.

6. Files with // prettier-ignore directives

Existing code uses // prettier-ignore to preserve a hand-aligned block. Agent edits inside the block — Prettier respects the ignore, but the agent’s careful alignment may not match the existing alignment.

How to spot it: grep -rn "prettier-ignore" <files> lists the protected zones. Cross-check with agent’s edited regions.

Before you start

  • Save a snapshot: git stash or git diff > before.patch so you can compare cleanly.
  • Note your Prettier version: cat package.json | grep '"prettier"' and pnpm exec prettier --version (use the project-local resolver, not global).
  • Note your editor’s Prettier integration: in VS Code, editor.formatOnSave, editor.defaultFormatter (esbenp.prettier-vscode), and prettier.requireConfig. Heads-up: since the Prettier VS Code extension v12.1.0 (Jan 2026) the extension no longer auto-checks that editor.defaultFormatter is set to Prettier and editor.formatOnSave is true, so confirm both explicitly instead of assuming they are wired up.

Information to collect

  • .prettierrc / prettier.config.js / package.json#prettier contents.
  • AGENTS.md sections about style.
  • The exact files reformatted on save (compare git diff lines vs what the agent wrote).
  • Editor of choice + extensions (VSCode + Prettier extension, etc.).
  • Whether prettier-eslint or eslint-config-prettier is installed.

Step-by-step fix

Ordered by ROI.

Step 1: Make Prettier part of the agent’s “done” loop

Put this in AGENTS.md (Codex reads it automatically at session start) or in the task prompt:

After every file edit, run:
  pnpm exec prettier --write <files-edited>

Confirm: pnpm exec prettier --check <files-edited>  returns 0.
If non-zero, fix and re-run before declaring done.

AGENTS.md is the durable home for this, per OpenAI’s Codex guidance — it loads into context on every run, so you do not have to repeat the rule in each prompt. Now the agent’s last action matches what your editor will produce: zero post-save reformat. On large repos, prettier --cache (cache is on by default in Prettier 3.6+) keeps the per-edit check fast.

Step 2: Align AGENTS.md style claims to .prettierrc

Open .prettierrc (or prettier.config.js) and translate every option into AGENTS.md:

## Formatting (matches .prettierrc — do not deviate)

- printWidth: 100
- semi: false       → no trailing semicolons
- singleQuote: true → use single quotes
- trailingComma: "all"
- arrowParens: "always" → (x) => x not x => x
- bracketSpacing: true → { foo } not {foo}

Codex’s first draft now matches Prettier’s output — eliminating the diff inflation.

Step 3: Pin the Prettier version the agent calls

Force the project’s pinned version:

After every edit:
  pnpm exec prettier --write <files>
  # NOT: npx prettier --write   (may download a newer Prettier, e.g. 3.8 vs your pinned 3.3)
  # NOT: prettier --write        (may resolve to a global install)

pnpm exec (or yarn exec / npm exec) routes to the version in your lockfile. Pin the exact version in package.json (for example "prettier": "3.8.4", not "^3.0.0") so the agent, your editor, CI, and your teammates all run byte-for-byte the same formatter.

Step 4: Resolve ESLint + Prettier conflicts permanently

The fix is eslint-config-prettier, which turns off every ESLint rule that overlaps with Prettier so the two tools stop fighting:

pnpm add -D eslint-config-prettier

If you are on ESLint 9 flat config (eslint.config.js, the default since ESLint 9), import the /flat entry and put it last in the array so it wins:

// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintConfigPrettier from 'eslint-config-prettier/flat'

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  eslintConfigPrettier, // must be last
]

On the legacy .eslintrc format, add 'prettier' last in extends:

extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier']

Do not use eslint-plugin-prettier to run Prettier as an ESLint rule. As of 2026 the Prettier maintainers discourage it: it makes ESLint slower, turns formatting differences into red squiggly “errors”, and gives the agent two formatters to satisfy. Keep the split clean — ESLint for correctness, Prettier for formatting. After this, eslint --fix and prettier --write produce identical output and stop reverting each other.

Step 5: Normalize invisible chars in agent output

Add to the task prompt:

Output rules:
- ASCII only in code (no smart quotes, no en-dashes, no nbsp)
- For comments: standard ASCII " ' - --
- For string literals: only what the user requested literally

For paranoia, run a sanitizer after edits:

sed -i 's/\xc2\xa0/ /g; s/\xe2\x80\x9c/"/g; s/\xe2\x80\x9d/"/g' <files>

Step 6: Handle prettier-ignore zones explicitly

In the task prompt, list protected zones:

The following blocks are `// prettier-ignore` protected — preserve their existing alignment exactly. Do not edit unless required:

- src/data/timezones.ts:42-78
- src/config/menus.tsx:120-180

If you must edit inside one, match the existing indent / alignment byte-for-byte.

Step 7: Optional — disable editor format-on-save during agent runs

If you cannot get the agent’s Prettier to match editor’s, temporarily:

"editor.formatOnSave": false

Then run Prettier yourself at the end of the session. Removes the editor as a hidden actor.

Verify

  • git diff on the agent’s output: line count should match what the agent reported, not 5x.
  • pnpm prettier --check <files> returns 0 immediately after agent finishes.
  • Open the file in your editor, save without typing anything: zero changes.
  • Pre-commit hook (lint-staged + prettier --check) passes on first try.

Long-term prevention

  • AGENTS.md formatting section is auto-generated from .prettierrc so they cannot drift.
  • Every agent task template includes “run prettier on touched files before done”.
  • Use pnpm exec (or equivalent) in agent tool calls — never global prettier.
  • Install eslint-config-prettier so ESLint never argues with Prettier.
  • Add a CI job that runs prettier --check on PR — it catches drift before merge.
  • For ASCII purity, add a pre-commit hook that rejects non-ASCII in .ts / .js files.

Common pitfalls

  • Running prettier --write on the whole project after an agent edit — masks which lines the agent actually touched and bloats the diff.
  • Editing AGENTS.md style section but forgetting to update .prettierrc (or vice versa) — they will fight again next month.
  • Trusting npx prettier without pnpm exec — version pinning is silently ignored.
  • Leaving editor.formatOnSave on while you “just check” the agent’s diff — the moment you click into the file, it gets reformatted.
  • Ignoring “trailing comma” disagreements as cosmetic — they break git blame and inflate every future diff.

FAQ

Q: I added Prettier to the agent loop but the editor still reformats on save. Why?

Two usual suspects. First, prettier.requireConfig: if it is false, the editor formats with Prettier defaults and ignores your .prettierrc. Set it true so the editor only runs Prettier when it finds a config. Second, since the Prettier VS Code extension v12.1.0 (Jan 2026) the extension stopped auto-confirming that it is the active formatter, so explicitly set "editor.defaultFormatter": "esbenp.prettier-vscode" and "editor.formatOnSave": true (scope per-language with "[typescript]" blocks if a different formatter is winning).

Q: We migrated to ESLint 9 flat config and eslint-config-prettier stopped working. What changed?

Flat config uses a different import. Use import eslintConfigPrettier from 'eslint-config-prettier/flat' and place it last in your export default [ ... ] array. The bare eslint-config-prettier (no /flat) and the extends: ['prettier'] string both belong to the old .eslintrc format and silently do nothing in flat config.

Q: My team uses Biome / dprint, not Prettier. Same fix?

Yes — substitute biome format --write or dprint fmt for the Prettier commands. The structural fix (formatter inside the agent loop + style hints aligned to config) is identical.

Q: Can I just tell Codex to not format at all?

You can, but then you owe it a clear “we will reformat outside the agent” workflow. The diffs will still be noisy on commit. Better to align the agent’s first draft to what Prettier will produce.

Q: What about .prettierignore?

Same story — make sure AGENTS.md lists patterns the agent should not touch, matching .prettierignore. Otherwise the agent edits a generated file that Prettier refuses to format and you get inconsistency.

External references

Tags: #Codex #agent #Troubleshooting #prettier #formatting