Codex patch 和现有代码冲突:6 个 state drift 来源 + 修复策略

「Patch hunk failed to apply」——Codex 读时和写时文件不一样。修 state drift,不要硬塞 patch。

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.autocrlftrue。或 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

相关阅读

标签: #Codex #Coding Agent #排查 #排查 #Patch 冲突