Codex 写出一份看着干净的 patch。你在编辑器里打开文件,Prettier-on-save 把每一行重排——引号翻转、加上 trailing comma、加分号或去分号、断行重布。git diff 变成实际改动的两倍长。更糟的是 agent 后续想继续接它自己写的代码时,对不上自己原本的行。Pre-commit hook fail,CI lint 报错。“改了 10 行”变成 300 行。
这不是 Codex 的 bug,是握手没做好。Agent 不知道有 .prettierrc,或者知道但 prompt 没加载它,或者跑了 format 但用的是默认设置不是项目设置。修法是让 Prettier 成为 agent”完成”定义的一部分,并把相关规则喂进 agent 的风格提示,让它第一稿就已经符合 Prettier 会产出的样子。
常见原因
按命中率排序。
1. Agent 没跑 Prettier,编辑器跑了
Agent 改完保存、宣布”done”。在你机器上编辑器 on-save Prettier 又重排了一遍。看着干净的输出最后一秒被改了形。
如何判断:git diff 比 agent 报告的大很多。跑 prettier --check <file>——会有不少行 fail。
2. Agent 跑的 Prettier 和项目的不是同一个
pnpm prettier --write vs npx prettier@3.2 --write vs 全局 prettier——不同版本产出不同。Codex 用了全局装的,但项目锁的是 3.0.x。
如何判断:项目里跑 npx prettier --version 和 agent 跑的不一样。Diff 是清一色的格式差(比如全部 ' → ")。
3. AGENTS.md 风格提示和 .prettierrc 不一致
AGENTS.md 说”用单引号”,但 .prettierrc 是 "singleQuote": false。Agent 听 AGENTS.md、保存时 Prettier 赢——两边在打架。
如何判断:手动对比 AGENTS.md 的风格主张和 .prettierrc / prettier.config.js。任何不一致 = 永远的吵闹 diff。
4. Prettier 和 ESLint 两个格式化器混用
ESLint 配 eslint-plugin-prettier + 独立 on-save Prettier,对 arrow paren、JSX trailing comma 排序略有不同。Agent 挑一个,工具链挑另一个。
如何判断:取决于最后跑哪个工具文件会不一样。跑 pnpm prettier --check . && pnpm eslint .——如果 prettier --write 后两个都过,但 eslint --fix 又把行改回去,就是有冲突。
5. Agent 输出了 Prettier 会改写的不可见字符
智能引号、不间断空格、en-dash——Codex 有时在 comment 或 string 里塞这些。Prettier(或某些插件)会标准化。
如何判断:cat -A <file> | grep -E '\xc2\xa0|\xe2\x80' 能找到非 ASCII。对比 agent 输出文本。
6. 文件有 // prettier-ignore 指令
现有代码用 // prettier-ignore 保留手对齐的块。Agent 在块内部改——Prettier 尊重 ignore,但 agent 的对齐和原对齐对不上。
如何判断:grep -rn "prettier-ignore" <files> 列出保护区,对照 agent 编辑的区域。
动手前先确认
- 存快照:
git stash或git diff > before.patch,方便清晰对比。 - 记 Prettier 版本:
cat package.json | grep '"prettier"'和npx prettier --version。 - 记编辑器的 Prettier 集成:VSCode 的
editor.formatOnSave+prettier.requireConfig设置。
需要收集的信息
.prettierrc/prettier.config.js/package.json#prettier内容。- AGENTS.md 里关于风格的章节。
- 保存时被重排的具体文件(对比
git diff行数和 agent 实际写的)。 - 用的编辑器 + 扩展(VSCode + Prettier 扩展等)。
- 是否装了
prettier-eslint或eslint-config-prettier。
最短修复路径
按收益从高到低。
Step 1:把 Prettier 写进 agent 的”完成”循环
任务 prompt 里:
每次改完文件,跑:
pnpm prettier --write <被改文件>
确认:pnpm prettier --check <被改文件> 返回 0。
非 0 就修完再说完成。
Agent 最后一动和编辑器会产出的一样——保存时零重排。
Step 2:AGENTS.md 风格主张对齐 .prettierrc
打开 .prettierrc(或 prettier.config.js),每个选项翻译到 AGENTS.md:
## 格式化(与 .prettierrc 一致 —— 不要偏离)
- printWidth: 100
- semi: false → 行尾不加分号
- singleQuote: true → 用单引号
- trailingComma: "all"
- arrowParens: "always" → (x) => x 不是 x => x
- bracketSpacing: true → { foo } 不是 {foo}
Codex 的第一稿就和 Prettier 输出一致——消除 diff 膨胀。
Step 3:钉死 agent 调用的 Prettier 版本
强制走项目锁的版本:
每次改完跑:
pnpm exec prettier --write <files>
# 不要:npx prettier --write (可能拉到别的版本)
# 不要:prettier --write (可能用全局装的)
pnpm exec / yarn dlx / npm exec 走项目 lock 的 Prettier。
Step 4:彻底解决 ESLint + Prettier 冲突
如果还没装就先装 eslint-config-prettier:
pnpm add -D eslint-config-prettier
ESLint config 最后 extends 它:
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier']
这条会禁用所有和 Prettier 冲突的 ESLint 规则,eslint --fix 和 prettier --write 输出一致。
Step 5:清理 agent 输出里的不可见字符
任务 prompt 加一段:
输出规则:
- 代码里只用 ASCII(不要智能引号、不要 en-dash、不要 nbsp)
- 注释:标准 ASCII " ' - --
- 字符串字面量:仅按用户字面要求
偏执一点的话改完跑一遍 sanitizer:
sed -i 's/\xc2\xa0/ /g; s/\xe2\x80\x9c/"/g; s/\xe2\x80\x9d/"/g' <files>
Step 6:显式处理 prettier-ignore 区域
任务 prompt 里列出受保护区:
以下块是 `// prettier-ignore` 保护区——按原对齐保留,非必要不要改:
- src/data/timezones.ts:42-78
- src/config/menus.tsx:120-180
必须改的话,缩进 / 对齐按字节对齐到现有的。
Step 7:可选——agent 跑期间关编辑器的 format-on-save
如果实在让 agent 的 Prettier 对不齐编辑器的,暂时:
"editor.formatOnSave": false
会话结束时自己跑 Prettier。把编辑器从隐藏 actor 拿掉。
怎么确认已经修好
- Agent 输出的
git diff:行数应该和 agent 报告一致,不是 5 倍。 - Agent 完成后立刻
pnpm prettier --check <files>返回 0。 - 在编辑器打开文件、什么都不动、保存:零变更。
- Pre-commit hook(
lint-staged+prettier --check)第一次就过。
长期预防
- AGENTS.md 格式化章节从
.prettierrc自动生成,它们就不会漂。 - 每个 agent 任务模板都包含”改完文件先 prettier 再完成”。
- Agent tool 调用里用
pnpm exec(或同类),不用全局prettier。 - 装
eslint-config-prettier,ESLint 不会和 Prettier 吵。 - 加 CI job 跑
prettier --checkPR 检查——merge 之前抓 drift。 - 想要 ASCII 纯度,加 pre-commit hook 拒收
.ts/.js里的非 ASCII。
常见坑
- Agent 改完之后整个项目跑
prettier --write——掩盖 agent 真正改了哪些行、diff 膨胀。 - 改了 AGENTS.md 风格章节但忘了改
.prettierrc(或反过来)——下个月又开始打架。 - 信任
npx prettier不走pnpm exec——版本锁定被静默忽略。 editor.formatOnSave开着的时候去”只是看看”agent diff——你一点进文件就被重排了。- 把 trailing comma 不一致当美观问题忽略——它打断
git blame、之后每个 diff 都膨胀。
常见 FAQ
Q:我把 Prettier 加进 agent 循环了,编辑器保存还在重排,为什么?
看编辑器设置里 prettier.requireConfig——为 false 时编辑器用默认设置、忽略 .prettierrc。设成 true 就只在找到 config 时跑 Prettier。
Q:我们团队用 Biome / dprint,不是 Prettier,修法一样吗?
一样——把 Prettier 命令换成 biome format --write 或 dprint fmt。结构性修法(formatter 进 agent 循环 + 风格提示对齐 config)完全一样。
Q:能让 Codex 直接别格式化吗?
可以,但你得有一套清晰的”agent 之外统一格式化”流程。Commit 时 diff 还是会吵。不如让 agent 第一稿就对齐 Prettier 产出。
Q:.prettierignore 呢?
同样的——确保 AGENTS.md 列出 agent 不该碰的 pattern,和 .prettierignore 一致。否则 agent 改了一个生成文件、Prettier 拒绝格式化——又是不一致。
相关阅读
- Codex 漏项目特定约定
- Codex 生成的代码不合现有 style
- Codex Agent 任务执行到一半就停
- Codex Agent 调用工具次数失控
- Codex patch 和现有代码冲突
- Codex 环境配置失败
- Codex Agent 跳过含二进制数据的文件:6 个原因 + probe 替代方案
- Codex 无法访问私有仓库:6 个权限层 + 排查清单
- Codex 写的 PR 描述太敷衍:怎么逼出结构化 before/after
- Codex 遇到 merge conflict 就卡住或瞎选边:rebase 策略和 AGENTS.md 兜底
- Codex 加了包但 lockfile 没动:怎么强制同步更新
- Codex 又造了一个和现有类型重名的 interface:怎么让它先搜再写
- Codex 说 “测试通过”,其实跳过了失败用例:如何强制诚实报告
- Codex 一遇大 diff 就停下来:怎么把任务切小