你让 Codex 在一个跑了很久的 branch 上继续干活。它开了头,然后停在 merge conflict in src/api/handler.ts。更糟的版本:它把还带着 <<<<<<< HEAD 标记的文件直接 commit 进去。再糟一点:它整段挑了错的那一边,把上周才 merge 进 main 的逻辑一股脑丢掉。
最快的修法: 在启动 Codex 任务之前就把 branch rebase 到基准 branch 上,并且把这个 rebase 放进环境的 setup 脚本里(setup 阶段有联网),而不是让 agent 自己去做(截至 2026 年 6 月,agent 阶段默认联网是 Off)。然后加一条 AGENTS.md 规则:“撞到 conflict 就停下来上报——绝不选边,绝不 commit marker”,再用一个 pre-commit hook 兜底。这样 Codex 根本碰不到 conflict,也就猜不错。
为什么这样有效:Codex 不是被训练来当 merge conflict 专家的。Model 能读冲突两边的内容,但缺少”每一边为什么存在”的语义 context,所以它只能猜。可靠的策略是让 agent 彻底接触不到 conflict。
有件事得先说清楚:截至 2026 年 6 月,Codex Cloud 的 PR 面板里没有任何”检测到冲突”的提示,也没有”修复冲突”按钮——这是一个还没实现的功能请求(openai/codex#23016,2026 年 5 月提交)。当 GitHub 的 PR 显示 This branch has conflicts that must be resolved 时,你是在 GitHub 上打开 PR 才发现的,Codex 的界面不会告诉你。所以别等着 agent 来提醒——直接把冲突挡在外面。
你属于哪一种情况?
| 症状 | 大概率原因 | 跳到 |
|---|---|---|
任务停在 merge conflict in <file> | branch 太老,agent 在过期的 tree 上跑 | 原因 1-2,Step 1-2 |
PR 里出现字面的 <<<<<<< / ======= / >>>>>>> 行 | Codex 把 marker 当普通文本 commit 了 | 原因 3,Step 3-4 |
PR 悄悄丢掉了刚 merge 进 main 的功能 | agent 整段挑了 ours/theirs | 原因 4,Step 3 |
| history 变成一堆 merge commit 的鼠窝 | agent 任务中途反复跑 git merge | 原因 5,Step 6 |
| 每次 rebase 同一个冲突都重新冒出来 | 没有缓存已记录的解决方案 | Step 7 |
常见原因
1. branch 落后基准 branch 好几天,撞 conflict 不可避免
你的 feature/x 是上周二切的,之后 main 又走了 40 个 commit。Codex 改任何东西都会撞冲突。Codex 会 clone 仓库、checkout 你为任务选的那个 branch;它不会先自动把这个 branch rebase 到基准 branch 上。
如何判断:git log --oneline origin/main..HEAD 没几条;git log --oneline HEAD..origin/main 一大坨。Branch 落后太多。
2. 任务开始前 branch 从来没被 rebase 过
你的 setup 脚本只跑 npm install,没跑 git rebase origin/main。于是 agent 在过期的 tree 上动手,冲突要等到它执行 apply_patch 时才浮现。
如何判断:setup 日志里没有任何 rebase / pull。第一个冲突出现在 apply_patch 这一步,而不是更早。
3. Codex 把 conflict marker 一起 commit 了
Model 看到 <<<<<<< HEAD ... ======= ... >>>>>>> main,没认出这是控制 marker,把它当成了普通文本。PR 里现在真的有 <<<<<<< 行。
如何判断:git diff origin/main..HEAD | grep -E '^\+(<<<<<<<|=======|>>>>>>>)' 有 match。build 也会挂。
4. agent 整段挑了一边
让它去解决冲突时,Codex 每个冲突点都全选 ours 或全选 theirs,根本没读内容。它挑的标准是哪边 diff 更小、或者哪边编译错更少,而不是哪边是对的。
如何判断:PR 把刚 merge 进 main 的功能掉了,或把本该由这个 branch 加上的工作丢了。和 git log 对照一下。
5. 用 merge 而不是 rebase,结果产生 merge 套 merge
Codex 跑了 git merge origin/main,撞冲突、修好了;后来 main 又有新 commit,它又跑一次 git merge origin/main,又撞冲突。history 变成 merge commit 的鼠窝,每个 merge 都带着自己的冲突。
如何判断:git log --oneline --graph 一堆 merge commit。history 难读。
最短修复路径
Step 1:启动任务前先 rebase
把它变成你自己的习惯,或者直接接进环境里:
git fetch origin
git rebase origin/main
# 如果撞冲突,在自己的 editor 里解,绝不在 Codex 里解
git push --force-with-lease
# 现在再启动 Codex 任务
agent 跑之前做一次干净的 rebase,就能消除绝大部分任务中途的冲突。
Step 2:把 rebase 放进 setup 脚本,而不是放给 agent
Codex Cloud 在你的任务前后会跑两段脚本:一段是容器创建时跑一次、有联网的 setup 脚本;另一段是恢复缓存容器时才跑的可选 maintenance 脚本(它存在的目的正是把过期的缓存 checkout 更新到最新)。这两个阶段跑在各自独立的 Bash session 里,所以你在 setup 里 export 的环境变量不会带进 agent 阶段。截至 2026 年 6 月,agent 阶段本身默认联网是 Off(Codex Cloud 环境设置里的开关是 Off / On,即使开成 On 也默认带域名白名单)——所以你没法指望 agent 去跑 git fetch。把需要联网的 rebase 放进 setup 或 maintenance 脚本里。
缓存容器最长保留 12 小时,并且只要你改了 setup 脚本、maintenance 脚本、环境变量或 secret(或者在环境页点 Reset cache),缓存就会自动失效。这也是 maintenance 脚本重要的原因:一个 6 小时前缓存下来的 checkout,可能已经落后 main 好几个 commit。
在 setup 脚本里(环境设置,或 CLI 用 .codex/setup.sh):
#!/usr/bin/env bash
set -euo pipefail
# branch 落后基准 branch 太多就拒绝开工
git fetch origin main --quiet
behind=$(git rev-list --count HEAD..origin/main)
if [ "$behind" -gt 50 ]; then
echo "ERROR: branch is $behind commits behind main. Rebase before launching Codex."
exit 1
fi
# 尝试一次干净的 rebase。撞冲突就 abort 并退出。
if ! git rebase origin/main; then
git rebase --abort
echo "ERROR: rebase produced conflicts. Resolve them manually before retrying."
exit 1
fi
npm ci
现在 agent 永远只在干净的 tree 上跑,冲突会在 agent 启动之前就上报给人。如果你用缓存容器,把同样的 git fetch + rebase 也镜像进 maintenance 脚本里,这样恢复的任务也能拿到 main 上的新 commit。
注意:setup 脚本返回非零
exit会让环境构建失败——这正是你想要的,宁可 setup 失败,也别要一个塞满 conflict marker 的 PR。
Step 3:AGENTS.md “撞到就停” 规则
把下面这段放进仓库根目录的 AGENTS.md。Codex 会按优先级拼出一条指令链——全局 ~/.codex/AGENTS.md,然后 git 根目录的 AGENTS.md,再往下每一级目录的 AGENTS.md,离被改文件越近的优先级越高——并会遵守这类操作规则。(同一层级的 AGENTS.override.md 优先于普通的 AGENTS.md,整条链默认上限是 32 KiB,所以把这条规则放靠前一点。)
## Merge conflict 处理
只要看到 conflict marker(`<<<<<<<`、`=======`、`>>>>>>>`),或
`git status` 显示文件处于 "both modified":
1. 立刻停下。
2. 不要编辑文件去删除 marker。
3. 没有人类的明确指示,不要选 `ours` 或 `theirs`。
4. 跑 `git rebase --abort` 或 `git merge --abort` 恢复干净状态。
5. 在 PR 描述里写明冲突的文件,以及引入冲突的是哪些 commit。
6. 把任务标记为 blocked。
绝不允许 commit 含有 `<<<<<<<`、`=======`、`>>>>>>>` 行的文件。
现在 agent 有了明确的退路,不需要再靠猜了。
Step 4:pre-commit hook 拦 conflict marker
Model 可能会无视一条文字规则,但 hook 是机械执行的。从 setup 脚本里装好它,这样每个缓存容器都带着它:
mkdir -p .git/hooks
cat > .git/hooks/pre-commit <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached -U0 | grep -E '^(\+|-)?\s*(<<<<<<<|=======|>>>>>>>)\s' >/dev/null; then
echo "ERROR: commit contains merge conflict markers. Resolve before committing."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
哪怕 agent 没听 AGENTS.md 的规则,这个 commit hook 也会拦住它。(注意:core.hooksPath 会覆盖 .git/hooks——如果你的仓库设了自定义 hooks 路径,就把 hook 写到那里去。)
Step 5:大型 agent 任务的前一晚手动 rebase
对于多步骤的 Codex 计划(一连串 4-5 个 PR),头天晚上就把 branch rebase 好。第二天 agent 走进一个干净的 tree,能省下好几个小时的冲突排查。
# 前一天
git fetch origin
git checkout feature/x
git rebase origin/main
git push --force-with-lease
# 确认 rebase 后的 branch CI 是绿的,再把第二天早上的 Codex run 排进队列
配合 branch protection,让 --force-with-lease 成为唯一被允许的 force 选项,这样 agent 在冲突中途也绝不可能抹掉队友的 push。
Step 6:rebase 优先于 merge
AGENTS.md 里:
和 main 集成时,永远用 `git rebase origin/main`,绝不用
`git merge origin/main`。你自己不要去跑 rebase——它会在任务开头由
setup/maintenance 脚本跑。如果任务进行中 main 又往前走了,不要
re-merge;停下来上报。
Rebase 让 history 保持线性。长任务里跑一次 merge 会产生冲突套冲突。
Step 7:用 git rerere 应对重复出现的冲突
如果同一个冲突在你每次 rebase 这个长寿 branch 时都重新冒出来,开启 rerere(reuse recorded resolution)。Git 会记下你解决某个冲突一次的方法,下次同样的冲突再出现时自动回放这个解决方案:
git config --global rerere.enabled true
这是给你自己用的——在自己的 editor 里解冲突,不是给 agent 用的。它消除的是你在把干净 branch 交还给 Codex 之前那些重复的体力活。
怎么确认已经修好了
在 Codex run 之前(和之后)在 branch 上跑这几条:
# 1. 所有 tracked 文件里没有任何 conflict marker
git grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- ':!*.md' || echo "clean"
# 2. branch 不落后基准 branch
git rev-list --count HEAD..origin/main # rebase 后应为 0
# 3. 没有 "both modified" 的文件
git diff --name-only --diff-filter=U # 应该什么都不打印
# 4. build / 测试通过
npm run build && npm test
四条都过,说明 agent 走进的是一个干净的 tree,PR 也没有冲突。
FAQ
我让 Codex 修 merge conflict,它为什么就是不修?
两个原因。第一,正确解决冲突需要知道”每一边为什么存在”,而这往往是 model 缺的 context——于是它只能猜,要么整段挑一边,要么把 marker 留在文件里。第二,在 Codex Cloud 里 agent 阶段默认联网是 Off(截至 2026 年 6 月),所以一个想 git fetch 最新 main 来理解另一边的 agent,根本连不上网。把 rebase 放进有联网的 setup 脚本里,两个问题都绕开了。
Codex 有没有一键自动解决 PR 冲突的按钮?
截至 2026 年 6 月没有。Codex Cloud 的 PR 面板里既没有”检测到冲突”的提示,也没有”修复冲突”按钮——这被记录为一个还没实现的功能请求 openai/codex#23016。你得在 GitHub 上打开 PR(看 This branch has conflicts that must be resolved 那条 banner)才能确认冲突。与其指望 agent 去检测冲突,不如用任务前的 rebase 把冲突挡掉。
rebase 到底该放在哪——setup 脚本还是 maintenance 脚本?
新容器放 setup 脚本;恢复的缓存容器放 maintenance 脚本。maintenance 脚本存在的意义就是更新那些缓存在较旧 commit 上的 checkout,所以把 git fetch + git rebase origin/main 也镜像到那里,能让恢复的任务保持最新。两者都有联网,agent 阶段则没有。
agent 把 <<<<<<< marker commit 了,build 也挂了,怎么救?
在 branch 上,如果 rebase/merge 还在进行中,先跑 git rebase --abort 或 git merge --abort。如果 marker 已经 commit 进去了,用 git grep -nE '^(<<<<<<<|=======|>>>>>>>)' 找出来,逐个文件手动修掉,再 amend 或加一个清理 commit。GitHub 自己在 PR 上的 Resolve conflicts 按钮在简单情况下能帮上忙,但一旦冲突太复杂(二进制文件,或冲突涉及的行数超过寥寥几行)这个按钮就会变灰,所以命令行才是稳的那条路。然后装上 Step 4 的 pre-commit hook,让这种事不再发生。
--force-with-lease 会不会覆盖队友的工作?
不会——这正是它的意义所在。git push --force-with-lease 在远端 branch 自你上次 fetch 之后又移动过的情况下会拒绝推送,所以它不会盖掉队友的 commit。而普通的 git push --force 会。再加一层 branch protection,只允许 --force-with-lease。
预防
- 启动 Codex 前先 rebase branch;在 setup 脚本里,branch 落后基准 branch 太多就直接 bail。
- 把需要联网的 rebase 放进 setup 或 maintenance 脚本(有联网),绝不放进 agent 阶段(默认联网 Off)。
- AGENTS.md “撞到 conflict 就停、绝不 commit marker、绝不挑边” 规则,scope 在仓库根目录。
- pre-commit hook 拦下任何含
<<<<<<</=======/>>>>>>>的 commit。 - rebase 优先于 merge;任务开头 rebase 一次,中途绝不 re-merge。
- 开启
git rerere,让你只需把重复出现的冲突解决一次。 - branch protection 加
--force-with-lease,让 agent 没法覆盖队友的工作。
相关
- Codex patch 和现有代码冲突
- Codex 没完成 patch
- Codex 改了 git history
- Codex 环境初始化失败
- Codex 一遇大 diff 就停
- Codex PR 太大
标签: #Codex #agent #排查 #conflicts