You let Claude Code run an autonomous task overnight; in the morning, 12 files are mangled, the new feature isn’t done, and the old one is broken. The most dangerous moment: you decide to “undo everything” — and a misplaced git checkout . or git reset --hard throws away the bits that were actually salvageable.
Fastest safe fix: before you touch anything, run git branch backup/before-rollback-$(date +%Y%m%d-%H%M) and git stash push -u. That snapshot makes every later command reversible. Then pick the smallest undo that does the job from the table below — start with git restore <file>, not git reset --hard.
This guide lays out six rollback situations ordered by scope (“smallest undo first”), with side-by-side syntax and a table of “what gets undone, what survives.” It then shows how git reflog rescues work after a reset --hard, --amend, or rebase you thought was final — with the one detail most guides get wrong: a lost commit is only safe for 30 days by default (gc.reflogExpireUnreachable), not 90, and a single git gc --prune=now can erase it sooner. Recover first, relax later.
Which bucket are you in?
Identify your situation before running anything — the right command depends entirely on how far the bad changes have traveled (working tree, commit, push, merge).
| Where are the bad changes? | Worst command you could run | Safe command | Section |
|---|---|---|---|
| Edited but not committed | git checkout . (silently nukes all) | git restore <file> or git restore -p | Cause 1 |
| Committed, not pushed | git reset --hard (loses good commits too) | git reset --soft HEAD~1 | Cause 2-3 |
| Pushed, not merged (PR open) | git push --force (clobbers a teammate’s push) | git push --force-with-lease | Cause 4 |
| Merged to main | any reset (rewrites shared history) | git revert <sha> | Cause 5 |
History rewritten (--amend/rebase) | running git gc (drops the lost commit) | git reflog then branch it | Cause 6 |
Common causes
Ordered by how urgent the rollback usually is.
1. AI did too much; you need to undo selectively
Most common. The agent ran for 20 minutes and changed 8 files. Three were what you wanted, five it invented. You want to keep three and discard five but don’t know how to undo per-file or per-hunk.
How to spot it: git diff --stat shows a long list of files; after review you can cleanly sort them into “keep” and “discard” piles.
2. Tests passed locally; CI failed
npm test is green locally, red on push. You suspect one of the AI commits introduced an environment-sensitive bug (path case, CRLF, dep version). You want to roll back to the last CI-green commit.
How to spot it: git log --oneline shows multiple AI commits; the last green CI build’s SHA is recoverable from GitHub Actions history or your CI dashboard.
3. You want the AI’s “plan” but not its “code”
The agent produced a reasonable execution plan (rename X, extract Y, add tests for Z), but the code quality was poor. You want to keep the commit messages / plan and throw the actual code changes away so another model can write them.
How to spot it: The commit messages / agent transcript are more valuable than the code diff.
4. AI changes are pushed but not merged
The PR is up on remote; no reviewer has approved yet. You want to force-reset your branch, but you’re worried about losing other commits if a teammate pushed in parallel.
How to spot it: git log @{u}..HEAD lists commits not yet on remote; git log HEAD..@{u} lists commits where remote is ahead.
5. AI changes are merged to main
The hardest case. Changes are on the main branch; teammates may have pulled. You can’t reset — only git revert to generate inverse commits.
How to spot it: git log main --grep="<AI commit>" shows the commit is on main.
6. AI ran git commit --amend or rebase and rewrote history
The agent executed git commit --amend or git rebase -i and the commit you wanted to keep is gone. Only reflog can save you here, and there’s a clock on it — the dropped commit is now “unreachable,” which means gc.reflogExpireUnreachable (default 30 days, as of June 2026) governs how long it survives, not the 90-day figure that applies to normal entries. Recover it today.
How to spot it: git log no longer shows a commit you’re certain existed yesterday.
Shortest path to fix
Step 1: Snapshot before you do anything
# Safety branch — recoverable even if every later command goes wrong
git branch backup/before-rollback-$(date +%Y%m%d-%H%M)
# Stash dirty working tree too
git stash push -u -m "before rollback $(date)"
git stash with -u includes untracked files; -m adds a message so you can find it later in git stash list.
Step 2: Pick the right command for the scope
This table covers 99% of cases. Confirm current state first, then pick a command.
| What to undo | Command | Undoes | Keeps |
|---|---|---|---|
| One file’s unstaged changes | git restore <file> | That file back to HEAD | Other files, staged area |
| All unstaged changes | git restore . | All unstaged edits | Staged area, untracked |
| Staged but not committed | git restore --staged <file> | Un-stages (keeps edits) | File contents |
| Last commit, keep changes | git reset --soft HEAD~1 | The commit | All edits in staged area |
| Last commit, edits in working tree | git reset HEAD~1 | Commit + stage | Edits in unstaged |
| Last commit, drop everything | git reset --hard HEAD~1 | Commit + edits | Nothing (but reflog saves you) |
| Selected hunks only | git restore -p <file> | Hunks you pick | Other hunks |
| Pushed + merged commit | git revert <sha> | Creates inverse commit | History intact |
Key distinction:
--soft= changes move to staged;--mixed(default) = unstaged;--hard= changes are removed from the tree (but reflog can still recover them)
Step 3: Use reflog to rescue “disappeared” changes
After reset --hard, --amend, or rebase, the “lost” work usually isn’t gone yet. Git keeps a per-repo log of every place HEAD ever pointed, and the commit object still lives in .git until garbage collection prunes it.
git reflog # every HEAD movement, newest first
# c9d3e5a HEAD@{0}: reset: moving to HEAD~1
# 8b4f2a1 HEAD@{1}: commit: AI: refactor user service
# 2e1c8d3 HEAD@{2}: commit: WIP
git branch rescue 8b4f2a1 # FIRST: make it reachable again (safest move)
git checkout rescue # then inspect it on a real branch
git reflog --since="2 hours ago" narrows the window and cuts noise. To inspect a single dropped commit before branching it, use git show 8b4f2a1 or git stash show for stashes.
How long do you have? This is where most guides are wrong. As of June 2026, Git’s defaults are:
| Entry state | Config key | Default lifetime |
|---|---|---|
| Reachable from a branch/HEAD | gc.reflogExpire | 90 days |
| Unreachable (dropped by reset/amend/rebase) | gc.reflogExpireUnreachable | 30 days |
Your reset --hard work falls in the 30-day bucket. Worse, an explicit git gc --prune=now (or some IDE/agent “cleanup” actions) can drop unreachable objects immediately. So the rule is: recover now, don’t run git gc until you have. If you want a longer safety net going forward, set git config --global gc.reflogExpireUnreachable 90.days.
Step 4a: Pushed but NOT merged — force with a lease, never raw force
The PR branch is yours and no one else is on it, so you can rewrite it — but use --force-with-lease, not --force. The lease aborts the push if a teammate (or your CI bot) pushed in the meantime, so you can’t silently clobber their work.
git reset --hard <good-sha> # roll your local branch back
git push --force-with-lease # refuses if remote moved unexpectedly
Check who’s ahead first with git log @{u}..HEAD (your unpushed commits) and git log HEAD..@{u} (commits the remote has that you don’t).
Step 4b: Pushed AND merged to main — use revert
If the commit is already on a shared branch like main, do not force push. Generate an inverse commit instead:
git revert <bad-sha> # single commit
git revert <bad-sha>^..<bad-sha2> # a range, oldest..newest
git revert -m 1 <merge-sha> # revert a merge commit
For a merge commit, -m 1 tells Git which parent is the “mainline” to keep — parent 1 is the branch you merged into (usually main), parent 2 is the branch that was merged in. Confirm the parents with git show <merge-sha> (the Merge: line lists them in order) before choosing -m 1 vs -m 2. revert produces a fresh commit that’s safe to push to a protected branch.
Step 5: Restore from the Step 1 snapshot if the rollback itself went wrong
git stash list
# stash@{0}: On main: before rollback 2026-05-22
git stash apply stash@{0} # apply but keep the stash
git stash pop stash@{0} # apply and drop the stash
# or just check out the safety branch
git checkout backup/before-rollback-20260522-1430
How to confirm it’s fixed
Don’t trust “it looks right” — verify the tree matches what you intended:
git status # clean tree, expected branch, no stray staged files
git diff HEAD # nothing here means working tree == last commit
git log --oneline -5 # the bad commits are gone (reset) or inverted (revert)
git diff <good-sha>..HEAD # empty == you are exactly back to the known-good state
Then run the cheapest real check your project has — npm test, npm run build, or just start the app. A rollback that leaves a dirty lockfile or a half-staged file is the second-most-common way this goes wrong (after reset --hard itself). Once green, delete the safety branch you no longer need: git branch -D backup/before-rollback-....
Prevention
- Working tree must be clean before letting the agent start:
git statusmust show “nothing to commit”; stash any dirt first - Add to CLAUDE.md / AGENTS.md: “commit after every atomic change; never bundle unrelated edits into one commit” — smaller commits, smaller rollback loss
- Prefix every agent commit with
AI:(git config commit.template); thengit log --grep="^AI:" --since="1 day"lists them all in one go - High-risk tasks (mass file deletion, lockfile changes, migrations) — tag before starting:
git tag wip/before-<task>; rollback becomes one command - Forbid agents from running
git reset --hard/git push --force/git rebase -idirectly — those should require a human keystroke - Enable GitHub branch protection on
main; disallow force push so that even if reflog expires, remote history is recoverable
FAQ
I ran git reset --hard and lost an uncommitted file the AI wrote. Can reflog get it back?
Only if it was ever committed or stashed. Reflog tracks commits and stash entries, not loose working-tree edits. A file that was written but never git add-ed and committed has no object in .git and cannot be recovered through Git — check your editor’s local history (VS Code: Timeline panel; JetBrains: Local History) instead.
How long do I actually have to recover a commit after reset --hard?
By default 30 days, not 90. The 90-day figure (gc.reflogExpire) is for entries still reachable from a branch. A commit dropped by reset, --amend, or rebase is unreachable and governed by gc.reflogExpireUnreachable, which defaults to 30 days as of June 2026 — and a manual git gc --prune=now can remove it immediately. Recover the same day.
What’s the difference between --soft, --mixed, and --hard on git reset?
All three move the branch pointer back. --soft leaves your changes staged; --mixed (the default) leaves them unstaged in the working tree; --hard discards them from the working tree entirely (recoverable only via reflog while the object survives). Use --soft when you just want to redo the last commit, --hard only when you truly want the edits gone.
Should I use git revert or git reset to undo a commit?
If the commit is only local, reset is cleaner. If it has been pushed to a branch others use (especially main), use revert — it adds an inverse commit and never rewrites shared history, so no one ends up with a diverged copy.
git push --force-with-lease got rejected. Why?
The lease detected that the remote branch moved since your last fetch — a teammate or CI pushed. That’s the safety net working. Run git fetch, review the new commits with git log HEAD..@{u}, reconcile them, then push again. Never reach for plain --force to “make it go through.”
Can I roll back just some of the AI’s changes in one file?
Yes — git restore -p <file> walks you through each hunk and asks keep or discard. For staged hunks, use git restore --staged -p <file>. This is the surgical tool for the most common case: the agent got three edits right and five wrong in the same file.
Related
- Git find old version
- Merge conflict after AI edits
- AI removed working logic during refactor
- Claude Code edited the wrong file
- AI pre-commit review workflow
- AI dependency upgrade workflow
Tags: #AI coding #Debug #Troubleshooting