Codex 生成 patch、apply 时挂:error: patch failed: src/utils.ts:42 / Hunk #1 FAILED at 42。或者 apply 完文件里留下 <<<<<<< 冲突标记。Codex 坚持 diff 是对的,git 坚持文件不一样——两边都对,因为 Codex 读的和现在磁盘上的不是同一份了。
Patch 冲突几乎都是「读和写之间状态漂了」。修漂移,不要硬塞 patch。四种主要漂移机制:文件在 session 中途变了、autoformat 触发、CRLF↔LF 转换、两个 agent / 人并行编辑。
常见原因
按命中率从高到低:
1. 文件在 Codex 读和 patch apply 之间变了
Codex 30 秒前读了 utils.ts,之后你在编辑器里 format 了一下(Prettier on save)。Patch 里的行号现在对不上。
如何判断:git status 看文件 mtime 是不是出乎意料地新。或者 git diff Codex 读的时间到现在——能看到意外改动。
2. autoformat 在 Codex 读之后跑了
VS Code 的「format on save」是隐形元凶。你只是切了一下窗口,但失焦保存触发了 Prettier,Codex 行号锚定的 patch 偏了 2 行。
如何判断:关掉 format-on-save("editor.formatOnSave": false),让 Codex 重读 + 重生成 patch——能 apply 就是它。
3. 空白 / 行尾不匹配(CRLF vs LF)
Windows clone repo 用了 core.autocrlf=true,本地是 CRLF,Codex 返回的 patch 是 LF——每一行都”差一个不可见空白”,git 拒收。
如何判断:git config core.autocrlf 是 true。或 file src/utils.ts 报「CRLF line terminators」——Codex 给的是 LF,不匹配。
4. Codex 读时和 apply 时不是同一个 commit
Session 开始时在 feature/billing,思考期间你切回 main,又让它 apply——patch 是对老 commit 的,新分支没有那些行。
如何判断:git log --oneline -5 看两个分支,分叉点在 Codex 读的文件之前——patch 对的是另一个过去。
5. 两个 agent / agent + 你 并行编辑
Claude Code 和 Codex 同时被让改 utils.ts,其中一个先完成,另一个的 patch 基于改之前的状态,冲突。
如何判断:git log src/utils.ts --oneline 多了你不记得的近期 commit、或 git status 显示未提交改动——协调失败。
6. Codex 给的是模糊 / 省略 diff
某些 Codex 模式返回的是 prose 风格 diff,里面有 // ... 占位符而不是真完整的 hunk——apply 时拼不回 // ... 该是什么,要么挂要么猜错。
如何判断:patch 文本里有字面 // ... 或 … (unchanged)——强制要求”no elision”。
最短修复路径
按收益从高到低,Step 1 一步覆盖 60% 的冲突。
Step 1:停 → 看现状 → 重生成
Hunk 挂了:
# 1. 看实际状态
git status
git diff src/utils.ts
# 2. working tree 有意外,找来源
git log --oneline -5 src/utils.ts
然后告诉 Codex:
Patch apply 失败。`src/utils.ts` 当前状态:
[贴前 50 行]
重读这个文件、重新生成 patch。
别硬塞过期 patch。
Step 2:agent session 期间关 autoformat
VS Code 设置:
{
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.codeActionsOnSave": {}
}
或 .vscode/settings.json 按 workspace 配。agent 完事后再打开(顺手跑一次 pnpm format 归一化)。
Step 3:repo 范围统一行尾
.gitattributes:
* text=auto eol=lf
*.bat text eol=crlf
*.png binary
*.jpg binary
然后:
git rm --cached -r .
git reset --hard
所有 checkout 都 LF,Codex patch 干净 apply。
Windows 开发者:git config --global core.autocrlf input(不是 true)。
Step 4:agent 跑前 commit,跑完再 commit
单 agent 纪律:
1. 启动 agent 前 `git status` 干净。
2. agent 改 + 测。
3. 下一个 session 前 `git add -A && git commit -m "agent: <task>"`。
多 agent 工作流,共享文件按”独占”对待——一时间一个 agent 改一个文件,通过 task tracker 或 CODEOWNERS-style claim 协调。
Step 5:强制 full-hunk diff,不要省略
Prompt 里写:
返回完整 patch 文本——每个被改函数的每一行。
不要 `// ...` 占位符、不要 `// (unchanged)`、不要省略。
patch 必须对当前文件干净 apply。
自动 apply 模式默认就是 full hunk;“返回 diff 让我手 apply” 模式必须显式禁省略。
Step 6:持续冲突就拆 patch
一份大 patch 反复挂,拆成原子小 patch:
不要一份大 patch。改成:
1. 加 `validate()` 方法——返回 patch 1。
2. 我 apply 完,改 call site——返回 patch 2。
3. 我 apply 完,改测试——返回 patch 3。
每份 patch 最多动 3 个文件。
小 patch 冲突 blast radius 也小,漂了好回。
预防建议
- 用
.gitattributes(* text=auto eol=lf)一次性统一行尾 - agent session 期间关
editor.formatOnSave,结束时刻意 format 一次 - 共享文件按独占对待——一时间一个 agent,通过 commit 协调
- 始终要求 Codex 给 full-hunk diff;省略 patch 省的 token 远抵不上冲突的代价
- 每次 agent run 前后都 commit;干净进 / 干净出
- 大重构拆成小 patch 链,不要一份巨型 patch