You open the Codex PR and the diff looks fine, but git log is wrong: the last three commits you made are gone, replaced by a single “WIP” commit you never wrote. Or Codex force-pushed after rebasing onto an older base, and a teammate’s commit disappeared. Or the agent amended a commit that was already on main, so everyone’s git pull now reports “divergent branches.”
Fastest fix (do this first): keep Codex on the default sandbox so it cannot reach the remote silently, then make the server reject rewrites. In ~/.codex/config.toml confirm sandbox_mode = "workspace-write" and network_access = false (both are the defaults as of June 2026) — that means any git push needs an explicit approval, so a force-push can’t happen behind your back. Then add a GitHub ruleset on main with Block force pushes and Restrict deletions turned on. Those two changes alone stop the disaster; the AGENTS.md rules below stop Codex from ever trying.
Why does this keep happening? Codex pattern-matches whatever git instructions it finds — your README, an old CONTRIBUTING.md, a Stack Overflow snippet pasted into a comment. If any of those say “always squash with git commit --amend” or “rebase onto main before pushing,” the agent treats it as policy and follows it, even on a shared branch.
The durable fix is three layers: explicit “never rewrite history” rules the agent actually reads, Codex’s own sandbox so a push requires approval, and git server protections that reject force-pushes no matter what.
Which bucket are you in?
| Symptom | Likely cause | Jump to |
|---|---|---|
PR has one WIP/squash commit but you made many | Squash-on-push default or an --amend loop | Causes 1, 4 |
| Teammate’s commit vanished after Codex pushed | Rebase onto stale base + force-push | Cause 2 |
| Review comment links 404; a commit SHA changed | Agent amended an already-pushed commit | Cause 3 |
| Uncommitted work or local commits gone, no push needed | git reset --hard / git clean from a script | Cause 5 |
git pull says “divergent branches” / “non-fast-forward” | Any history rewrite that already reached the remote | Recover |
Common causes
1. Old README or CONTRIBUTING.md still recommends git commit --amend
Years ago you wrote “squash fixups with git commit --amend --no-edit before pushing.” The doc is still there. Codex reads it, follows it, and amends commits that have already been pushed.
How to spot it: run grep -rin -E 'amend|rebase -i|force.push' README.md CONTRIBUTING.md docs/. If the agent’s transcript quotes those lines, that is the source.
2. Agent rebased onto main to “resolve conflicts”
Codex saw a merge conflict and decided a rebase was tidier. After rebasing it force-pushed. Anyone with the old branch checked out now has divergent history.
How to spot it: look in the transcript for git rebase, git push --force, or git push -f. Compare the branch reflog before and after with git reflog show <branch>.
3. Agent amended to “fix a typo in the previous commit”
The model treated commits as drafts. It noticed it forgot a file, ran git add forgotten.ts && git commit --amend --no-edit, then force-pushed. The original commit’s SHA is gone, so any review comment referencing it now 404s.
How to spot it: read git reflog. If you see a commit (amend) entry after a push, this is it.
4. Squash-on-push default in the agent harness
Some Codex wrappers default to “squash all task commits into one before pushing.” If you wanted the intermediate commits for review, they are now gone.
How to spot it: the PR has exactly one commit even though the transcript shows many distinct edits. Check your harness config for a squash_commits: true (or similar) key.
5. Agent ran a destructive cleanup script
You have a scripts/cleanup.sh that does git reset --hard origin/main. The agent ran it thinking it would “clean state,” wiping uncommitted work and any local commits.
How to spot it: the transcript contains git reset --hard, git clean -fdx, or a call to a project script that does either.
Shortest path to fix
Step 1: Add an explicit AGENTS.md rule (in the closest file)
Codex reads AGENTS.md, not the legacy codex.md. It builds one instruction block per session by concatenating every AGENTS.md from your git root down to the working directory, plus the global ~/.codex/AGENTS.md. Files are joined root-first, leaf-last, and the file closest to the working directory wins because it appears last in the combined prompt (as of Codex CLI in June 2026). So put these rules in the repo-root AGENTS.md (or a nearer one if a subdirectory has its own), not buried in an old README:
## Git rules (mandatory)
- Never run `git commit --amend` for any reason.
- Never run `git rebase`, `git rebase -i`, or `git reset --hard`.
- Never run `git push --force`, `git push -f`, or `git push --force-with-lease`.
- Always create new commits on top of the current branch tip.
- If a merge conflict appears, stop and report it. Do not rewrite history to avoid it.
- To "fix" a previous commit, write a NEW commit. To undo, use `git revert`.
The combined AGENTS.md is capped at 32 KiB (tunable via project_doc_max_bytes), so keep the rules tight. If a stale doc at the same level keeps overriding you, an AGENTS.override.md at that level silently suppresses the sibling AGENTS.md.
Step 2: Audit existing docs for stale advice
Find and update every mention of history rewriting:
grep -rn -E 'commit --amend|rebase -i|push --force|reset --hard' \
README.md CONTRIBUTING.md docs/ .github/
Either delete the lines or prefix them with “DO NOT” and a pointer to AGENTS.md. Old positive advice is worse than no advice for an agent — it reads it as a current instruction.
Step 3: Keep Codex’s sandbox tight so a push needs approval
This is the native control most people miss. In the default workspace-write sandbox, network access is off, so any command that needs the network — including git push — is blocked until you approve it. Check ~/.codex/config.toml:
sandbox_mode = "workspace-write" # default; do not switch to danger-full-access
approval_policy = "on-request" # or "untrusted"; never blanket "never"
[sandbox_workspace_write]
network_access = false # default; a push can't reach the remote silently
With these defaults, Codex pauses and asks before it can push at all, so you see the command before history moves. The failure mode to avoid: running with --dangerously-bypass-approvals-and-sandbox (or sandbox_mode = "danger-full-access" + approval_policy = "never"), which removes every guardrail. Use full access only inside a throwaway VM, never on a clone of a shared repo.
Step 4: Protect the branch on GitHub with a ruleset
GitHub’s modern mechanism is Rulesets (Settings → Rules → Rulesets → New branch ruleset). Older repos may still use Settings → Branches → Branch protection rules; either works, and a fresh branch protection rule already disables force pushes and deletions by default. For main and any shared release branch, enable:
- Require a pull request before merging
- Require status checks to pass
- Restrict who can push to matching branches (ideally no one directly — only via PR)
- Block force pushes
- Restrict deletions
This is the safety net. Even if Codex tries to force-push, the server rejects it with remote rejected ... protected branch hook declined.
Also add the pattern codex/* (and agent/* if you use it) to the ruleset’s target branches — those are the branches Codex pushes to. If two parallel tasks land on the same branch, a force-push wipes one of them.
Step 5: Block non-fast-forward at the sandbox layer with a pre-push hook
Some agent setups let you install client-side hooks during environment setup. In .codex/setup.sh (or your harness’s setup script):
mkdir -p .git/hooks
cat > .git/hooks/pre-push <<'EOF'
#!/usr/bin/env bash
# Block force-push / amend-after-push from the agent sandbox
zero="0000000000000000000000000000000000000000"
while read -r local_ref local_sha remote_ref remote_sha; do
if [[ "$remote_sha" != "$zero" ]]; then
if ! git merge-base --is-ancestor "$remote_sha" "$local_sha"; then
echo "ERROR: non-fast-forward push blocked. Do not rewrite history."
exit 1
fi
fi
done
EOF
chmod +x .git/hooks/pre-push
This refuses any non-fast-forward push at the sandbox layer, before it ever reaches GitHub. (A determined git push --no-verify skips it, which is why Steps 3 and 4 still matter.)
Step 6: Review git log and the timeline before clicking merge
Add this to your PR review checklist:
- [ ] `git log --oneline origin/main..HEAD` shows the commits I expect
- [ ] No `(amend)` entries in `git reflog` for this branch
- [ ] No commits from people other than the agent on this branch
- [ ] Force-push count on the PR is 0 (GitHub shows "force-pushed" in the timeline)
Two seconds per PR, and it catches every variant above.
How to recover history that was already rewritten
If Codex already force-pushed and a commit is “gone,” it is almost certainly recoverable for a while:
- Find the old tip locally. On any clone that still has the pre-rewrite branch, run
git reflog show <branch>(orgit reflog) and copy the SHA from before the rewrite. Thengit branch recovered-work <sha>and push that to a new branch. - No local copy? Check GitHub’s reflog for the ref: the event log under the branch, the PR’s “force-pushed” timeline entry (it links the old SHA), or the repo’s Activity view (Insights → Network / the branch’s Activity). Visiting
https://github.com/<owner>/<repo>/commit/<old-sha>often still resolves a dangling commit. - Restore forward-only. Once you have the old SHA, create a new commit or branch from it. Do not force-push the recovered state back over the corrupted one — make a clean PR so you do not rewrite history a second time.
- Lost commits stay reachable until git garbage collection runs (local default is ~30 days for reflog entries). Recover sooner rather than later.
How to confirm it’s fixed
git push --force origin mainfrom a test clone should fail withprotected branch hook declined(ruleset is live).- Ask Codex to “amend the last commit” in a scratch repo — it should refuse or stop and report, citing the
AGENTS.mdrule. - Trigger a
git pushinside a Codex task: it should surface an approval prompt rather than pushing silently (network sandbox is on). git reflog show <branch>over the last few agent PRs shows no(amend)orrebaseentries.
Prevention
- Keep the “no history rewrite” rules in the closest
AGENTS.md(root or subdir), not in a README. - Sweep README/CONTRIBUTING for outdated git advice every quarter.
- Add a GitHub ruleset for
mainplus everycodex/*andagent/*branch pattern. - Leave the Codex sandbox on
workspace-writewithnetwork_access = false; never usedanger-full-accesson a shared clone. - Install a
pre-pushhook that blocks non-fast-forward pushes. - Review
git logand the GitHub timeline before merging agent PRs. - Use
git revertto undo, never--amendorreset --hard.
FAQ
Codex already force-pushed and a commit is gone. Can I get it back?
Usually yes. Use git reflog show <branch> on any clone that still has the old tip, grab the SHA from before the rewrite, and branch from it. If no clone has it, the PR’s “force-pushed” timeline entry on GitHub links the old SHA, which often still resolves. See How to recover above.
Will a GitHub ruleset stop Codex from rewriting history on its own feature branch?
Only if you target that branch pattern. A ruleset on main does not protect codex/feature-x. Add codex/* (and agent/*) to the ruleset so parallel tasks can’t force-push over each other.
Why does Codex rewrite history when I never asked it to?
It read an instruction somewhere — usually an old README or CONTRIBUTING.md that says “squash with --amend” or “rebase before pushing” — and treated it as project policy. Audit those docs (Step 2) and put a louder rule in AGENTS.md (Step 1).
Does the AGENTS.md rule alone prevent this? It is the single biggest lever, but it is a soft control: an instruction, not a hard block. Pair it with the sandbox network setting (Step 3) and the server-side ruleset (Step 4) so a mistake can’t actually reach shared history.
Is git push --force-with-lease safe for the agent to use?
No. It is safer than --force for a human who knows the remote state, but it still rewrites history, and an agent can satisfy the “lease” check and still clobber work. Keep all three force variants in the AGENTS.md ban list.
How do I undo a bad commit without rewriting history?
Use git revert <sha>. It creates a new commit that inverts the bad one, leaving the original SHA intact so review links and teammates’ clones stay valid. For a merge commit, git revert -m 1 <merge-sha>.
Related
- Codex cannot finish patch
- Codex patch conflicts existing code
- Codex cannot resolve merge conflict
- Codex PR too large
- Codex runs tests but skips failures
- Codex misses project conventions
External references: Codex sandboxing & approvals · GitHub rulesets
Tags: #Codex #agent #Troubleshooting #git