You let Claude Code refactor a helper, it rewrote the whole file, you committed — and only then noticed it deleted a 5-line special-case branch that was actually in production use. Or you just ran git reset --hard and reset the wrong commit. Or the remote got rebased and your old commits seem to have vanished after git pull. 99% of the time the code isn’t actually gone — the refs no longer point at it, but the git objects sit in your repo for at least 30 days. This article walks through the three common recovery paths: restoring from file history, recovering HEAD with reflog, and pulling dangling commits with fsck.
Common causes
Ordered by hit rate, highest first.
1. AI or manual commit overwrote part of a file
You ran git add . && git commit -am "..." and an AI agent’s rewrite blew away lines you needed. This is the most common AI-coding loss — especially when the prompt didn’t say “only modify function X.”
How to spot it: git log --oneline -5 -- path/to/file.ts to see the file’s recent commits; git diff HEAD~1 -- path/to/file.ts to see what the last commit actually changed.
2. git reset --hard moved HEAD too far
You meant to undo the last commit and typed git reset --hard HEAD~3 instead — three commits gone.
How to spot it: First line of git reflog reads HEAD@{0}: reset: moving to ....
3. Branch was rebased / force-pushed
A teammate (or you) ran git rebase main then git push --force, all the old commit hashes changed, and after git pull your old commits are orphaned.
How to spot it: The hashes in git log origin/branchname --oneline after git fetch don’t match what’s in your git reflog.
4. Branch deleted by mistake
git branch -D feature-x removed an unmerged branch; its tip commit is now dangling.
How to spot it: git reflog show feature-x errors, but git reflog | grep feature-x still has the last checkout hash.
5. Stash overwritten or dropped
git stash pop hit a conflict, you panicked and ran git stash drop without resolving; or git stash clear after multiple stashes.
How to spot it: git fsck --unreachable | grep commit — among the dangling commits, look for timestamps near when you stashed.
Shortest path to fix
Pick the path that matches your loss type.
Step 1: Snapshot the current state first
Whatever path you take next, always start with:
git stash push -u -m "before-recovery-$(date +%s)"
# or, safer
git branch backup/before-recovery-$(date +%s)
That way if recovery goes sideways, you can git stash pop or git checkout backup/... to get back to now.
Step 2: Path A — you know which file lost content
The most common case. Use log -p to see the file’s full history:
git log -p --follow -- path/to/file.ts
--follow traces renames. Scroll to the version you want, note its SHA. Then:
# Restore that version to your working tree (nothing else changes)
git checkout <sha> -- path/to/file.ts
# Or just view it
git show <sha>:path/to/file.ts > /tmp/oldversion.ts
diff /tmp/oldversion.ts path/to/file.ts
If you only need a few lines from the old version, copy-paste is cleaner than cherry-pick.
Step 3: Path B — entire HEAD was reset / rebased away
git reflog is git’s local operation log, recording every HEAD move:
git reflog
# Example output:
# a3f7c1d HEAD@{0}: reset: moving to HEAD~3
# 9b2e8f4 HEAD@{1}: commit: fix auth bug
# 7c4a1d2 HEAD@{2}: commit: add login form
# ...
Find the pre-reset hash (here 9b2e8f4), then:
# Inspect it
git show 9b2e8f4
# Move HEAD back there
git reset --hard 9b2e8f4
# Or create a branch off it without disturbing current
git branch recovered-state 9b2e8f4
Step 4: Path C — reflog can’t find it either (deleted branch, dangling commit)
If even reflog doesn’t have it (another clone, or reflog was gc’d), scan for dangling objects with fsck:
git fsck --lost-found
# Example:
# dangling commit a1b2c3d...
# dangling commit e4f5a6b...
# Inspect each
git show a1b2c3d
git show e4f5a6b
# Once you find it, branch or checkout
git branch recovered a1b2c3d
Sorting by time is faster:
git fsck --unreachable --no-reflogs | grep commit | \
awk '{print $3}' | \
xargs -I{} git log -1 --format='%ci %H %s' {} | sort -r
Step 5: Path D — use git bisect to find the right version
If you only know “it worked two weeks ago, doesn’t now” but not which commit broke it:
git bisect start
git bisect bad HEAD # current is broken
git bisect good v1.4.0 # this tag was good
# git auto-checks out a middle commit; test, then:
git bisect good # or git bisect bad
# When done:
git bisect reset
Once you’ve found the breaking commit, use the Step 2 method to restore the file to its pre-break version.
Step 6: Push the recovered commit before gc eats it
A recovered dangling commit is unreffed and git gc will sweep it after ~30 days. Push it to remote immediately to give it a ref:
git branch recovery/found-it <sha>
git push origin recovery/found-it
Prevention
- Before any AI refactor,
git commit -am "checkpoint before AI refactor"so the agent has a known-good fallback - Enable IDE auto-save + Local History (VS Code / Cursor have this by default) as a second safety net independent of git
git pushto a remote or personal backup branch at end of day — reflog is local only and disappears with the disk- After a critical change, push to a
wip/branch before continuing, so you don’t squash mid-state commits you might need - Forbid
force pushto shared branches (main / develop) in team policy; use--force-with-lease - Set
git config gc.reflogExpire 90.days.agoto extend reflog retention from 90 days further
Related
- AI rollback changes
- How to inspect AI-generated diffs
- Multiple AI agents created conflicts
- AI pre-commit review workflow
- AI dependency upgrade workflow
Tags: #AI coding #Debug #Troubleshooting