Recovering an Old File Version After Commit

You committed over what you wanted to keep. Three paths back. Usual causes: accidental commit overwrite; hard reset moved head. Start with: `git log -p -- <file>` to see the file's history.

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 push to 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 push to shared branches (main / develop) in team policy; use --force-with-lease
  • Set git config gc.reflogExpire 90.days.ago to extend reflog retention from 90 days further

Tags: #AI coding #Debug #Troubleshooting