Codex 自作主张改了 git history:amend、rebase、force-push 怎么禁掉

Codex 跑了 git commit --amend 或交互 rebase,重写了共享分支的 history。如何把 agent 锁死在 forward-only commit 上。

你打开 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 rebasegit push --forcegit 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 --hardgit 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,不用 --amendreset --hard

相关

标签: #Codex #agent #排查 #git