你打开 Codex 的 PR,diff 看着没问题,但 git log 不对:你最近三个 commit 没了,被一个你没写过的 “WIP” commit 替掉了。或者 Codex rebase 到老的 base 之后 --force 推了,同事的一个 commit 凭空消失。或者它 amend 了一个已经在 main 上的 commit,所有人本地 clone 都崩了。
Codex agent 会 pattern match 它能看到的所有 git 指引——你的 README、老的 CONTRIBUTING.md、注释里贴的 stack-overflow 片段。只要任何一处写着 “提前 squash 用 git commit --amend” 或 “push 前 rebase 到 main”,agent 就会照做,连共享分支也不放过。
修复要两层:让 agent 看得到的 “不允许重写 history” 规则,加上 git server 那边无论如何都拒绝 force-push 的保护。
常见原因
1. 老的 README 或 CONTRIBUTING.md 还在推荐 git commit --amend
几年前你写过 “push 前用 git commit --amend --no-edit squash fixup”。文档还在。Codex 读到了照做,amend 已经 push 过的 commit。
如何判断:grep -ri 'amend\|rebase -i\|force-push' README.md CONTRIBUTING.md docs/。如果 agent transcript 引用了这些行,源头就是它们。
2. Agent 用 rebase 来 “解决冲突”
Codex 看到 merge conflict,觉得 rebase 干净。Rebase 完 --force push。本地还 checkout 着旧分支的人现在 history 完全分叉。
如何判断:transcript 里搜 git rebase、git push --force、git push -f。对比 reflog before/after。
3. Agent amend 来 “修上一个 commit 的 typo”
Model 把 commit 当成草稿。它发现某个文件忘加,git add forgotten.ts && git commit --amend --no-edit,然后 --force。原 commit 的 SHA 没了——任何 review comment 引用都 404。
如何判断:对比 git reflog。看到 push 后面跟着 commit (amend) 就是它。
4. Agent harness 默认 squash-on-push
有些 Codex harness 默认 “把 task 的所有 commit squash 成一个再 push”。你本来想留中间 commit 给 review 的,现在都没了。
如何判断:transcript 里能看出多次独立编辑,PR 却只有一个 commit。检查 Codex/agent harness 配置里有没有 squash_commits: true。
5. Agent 跑了破坏性 cleanup 脚本
你的 scripts/cleanup.sh 里有 git reset --hard origin/main。Agent 以为这是 “清状态”,跑下去把未提交工作 + 本地 commit 全擦了。
如何判断:transcript 包含 git reset --hard、git clean -fdx、或者调用了项目自己的清理脚本。
最短修复路径
Step 1:AGENTS.md 写死禁令
放在 AGENTS.md 靠前位置:
## Git 规则(强制)
- 任何情况都不许跑 `git commit --amend`。
- 不许跑 `git rebase`、`git rebase -i`、`git reset --hard`。
- 不许跑 `git push --force`、`git push -f`、`git push --force-with-lease`。
- 始终在当前 branch tip 上 append 新 commit。
- 出现 merge conflict 就停下来上报,不许用重写 history 的方式绕过。
- 要 "改" 前一个 commit,写新 commit。撤销用 `git revert`。
Codex 看第一条匹配的规则。把这些放文件顶部就比老 README 的优先级高。
Step 2:清掉旧文档里的过时建议
找出所有重写 history 的相关条款:
grep -rn -E 'commit --amend|rebase -i|push --force|reset --hard' \
README.md CONTRIBUTING.md docs/ .github/
要么删行,要么加 “DO NOT” 前缀指向 AGENTS.md。对 agent 来说,老的正向建议比没建议更糟。
Step 3:GitHub branch protection
repo Settings → Branches,对 main 和所有共享 release branch:
- 必须走 PR 才能 merge
- 必须 status check 通过
- 限制谁能直接 push(最好谁都不能,只走 PR)
- 禁 force push
- 禁删除
这是保底。Codex 想 force-push,server 直接拒绝。
也可以保护 codex/* 和 agent/* 这类 branch pattern——Codex 推的就是这些。两个并行 task 撞到同一个 branch 一 force-push 就互相覆盖。
Step 4:setup.sh 里装 pre-push hook
有的 agent sandbox 允许装 client hook。.codex/setup.sh:
mkdir -p .git/hooks
cat > .git/hooks/pre-push <<'EOF'
#!/usr/bin/env bash
# 在 sandbox 层拒绝 force-push / amend-after-push
while read local_ref local_sha remote_ref remote_sha; do
if [[ "$remote_sha" != "0000000000000000000000000000000000000000" ]]; then
if ! git merge-base --is-ancestor "$remote_sha" "$local_sha"; then
echo "ERROR: non-fast-forward push 已拦截,不要重写 history。"
exit 1
fi
fi
done
EOF
chmod +x .git/hooks/pre-push
任何非 fast-forward push 在 sandbox 这层就被拒,根本到不了 GitHub。
Step 5:merge 前看一眼 git log
PR review checklist 里加:
- [ ] `git log --oneline origin/main..HEAD` 显示的 commit 是我预期的
- [ ] 该 branch 的 `git reflog` 没有 `(amend)` 条目
- [ ] 该 branch 上没有 agent 之外的人的 commit
- [ ] PR 的 force-push 次数为 0(GitHub timeline 会显示 "force-pushed")
每个 PR 两秒,所有变种都能抓到。
预防
AGENTS.md顶部固定 “不许重写 history” 规则- 每季度扫一遍 README/CONTRIBUTING 里过时的 git 建议
main+ 每个codex/*/agent/*pattern 都开 branch protection.codex/setup.sh里装 pre-push hook 拦 non-fast-forward- merge agent PR 前看
git log和 GitHub timeline - 撤销用
git revert,不用--amend或reset --hard