Recovering unreachable objects with git fsck Jump to heading

The reflog is the first recovery tool, but it only remembers what a ref once pointed to. Plenty of lost work never had a ref: a file you staged with git add but never committed before a git reset --hard, a stash you dropped, a commit created by a script in a detached HEAD, objects from a branch deleted on another machine and fetched in a bundle. All of these may still exist in the object store as unreachable objects. git fsck can list them, and with some filtering you can find the one you need among hundreds. This page shows how, and how to rescue what you find before garbage collection removes it, within history rewriting and recovery.

When to use this approach Jump to heading

  • Work was lost and the reflog does not show it β€” see recovering lost commits with git reflog first.
  • You staged changes, then ran git reset --hard or git checkout -- . and lost them.
  • A stash was dropped or cleared, and git stash list no longer shows it β€” a case covered in more depth in recovering a dropped stash.
  • You suspect commits exist in the repository but no branch, tag or reflog reaches them.

Step 1 β€” Stop anything that might garbage-collect Jump to heading

Unreachable objects are deleted by git gc, which Git also runs automatically after some commands. Before searching, prevent automatic collection and make a copy of the repository’s object directory if the lost work matters.

git config gc.auto 0                # no automatic gc in this repository for now
git maintenance unregister 2>/dev/null || true
cp -a .git/objects /tmp/objects-backup-$(date +%s)

⚠️ SAFETY WARNING: Do not run git gc, git prune or git maintenance run while recovering. Each can permanently delete the unreachable objects you are looking for. Restore gc.auto (git config --unset gc.auto) only after you have rescued everything you need.

What the reflog can recover and what needs fsckThe reflog recovers anything a ref once pointed to: old branch tips, pre-rebase commits, resets. It knows nothing about staged-but-uncommitted files, dropped stashes after the stash reflog is cleared, or commits made without updating any ref. Those need fsck.git refloggit fsckreset away commitsyesyesdropped stashuntil stash reflog clearedyesstaged, never committednoyes, as blobsdetached-HEAD commitsHEAD reflog onlyyestry the reflog first; fsck is slower but sees everything not yet pruned

Step 2 β€” List unreachable objects Jump to heading

git fsck --unreachable lists every object no ref reaches. --no-reflogs also treats objects reachable only from reflogs as unreachable, which widens the search to things reflogs remember but you have not found yet.

git fsck --unreachable --no-reflogs 2>/dev/null | awk '{print $2}' | sort | uniq -c
#   312 blob
#    41 commit
#    87 tree
What kinds of lost work fsck can findUnreachable commits include dropped stashes, deleted branch tips and detached-HEAD work. Unreachable blobs include file contents that were staged but never committed. Unreachable trees are directory snapshots, useful mainly for reconstructing a staged state.commitdropped stashdeleted branch tipblobstaged, nevercommitted filestreea stageddirectory statestaged-but-uncommitted work survives only as blobs β€” fsck is the only way back to it

Step 3 β€” Find lost commits, including stashes Jump to heading

List unreachable commits with their dates and subjects. Stash commits have subjects starting with β€œWIP on” or β€œOn”, which makes them easy to spot.

git fsck --unreachable --no-reflogs 2>/dev/null | awk '$2=="commit"{print $3}' |
while read -r c; do git log -1 --format="%h %ci %s" "$c"; done | sort -k2 -r | head -20
# 9f1c2e7 2026-10-02 14:18:02 +0200 WIP on feature/invoice-pdf: 8b3e4f2 Add PDF renderer
# 3ad04b1 2026-10-01 17:40:55 +0200 Add page numbering to invoices
# Inspect a candidate, then rescue it under a name
git show --stat 9f1c2e7
git branch rescued/stash-0210 9f1c2e7          # a stash commit can be applied with git stash apply 9f1c2e7

Step 4 β€” Find lost staged files among blobs Jump to heading

Blobs have no names or dates; only content. Search them by a string you remember from the file, or by size, and write matches out for inspection.

mkdir -p /tmp/rescued
git fsck --unreachable --no-reflogs 2>/dev/null | awk '$2=="blob"{print $3}' |
while read -r b; do
  if git cat-file -p "$b" | grep -q 'def render_page_numbers'; then
    git cat-file -p "$b" > "/tmp/rescued/$b.py"; echo "match: $b"
  fi
done

git fsck --lost-found does a similar job automatically: it writes every dangling blob into .git/lost-found/other/ and every dangling commit into .git/lost-found/commit/, which you can then search with ordinary tools.

From lost work to a rescued fileDisable automatic garbage collection, list unreachable objects with fsck, filter commits by subject and date or blobs by remembered content, inspect the candidates, and give each rescued object a ref or write it to a file.Freeze gcgc.auto 0fsck--unreachable --no-reflogsFiltersubject / contentInspectshow / cat-fileRescuebranch or filegiving a rescued commit a branch makes it reachable, so gc can no longer remove it

Step 5 β€” Restore the staged state, then clean up Jump to heading

If you lost an entire staged state β€” many files added before a reset β€” an unreachable tree may hold it as a whole. Trees written by git stash or by git write-tree during commands can be read back into the index.

# Find trees containing a path you remember
git fsck --unreachable --no-reflogs 2>/dev/null | awk '$2=="tree"{print $3}' |
while read -r t; do git ls-tree -r --name-only "$t" | grep -q '^src/invoice/pdf.py$' && echo "$t"; done

# Load a candidate tree into a scratch index and inspect it
GIT_INDEX_FILE=/tmp/rescue-index git read-tree <tree-sha>
GIT_INDEX_FILE=/tmp/rescue-index git diff --cached --stat HEAD

When everything is rescued, re-enable automatic maintenance.

git config --unset gc.auto
git maintenance register 2>/dev/null || true

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Why does fsck find hundreds of objects I never lost? Jump to heading

Normal Git operation creates unreachable objects all the time: amended commits, rebased commits, intermediate blobs from git add. Most are harmless leftovers. Filtering by date, subject and content is how you find the one that matters.

How long do unreachable objects survive? Jump to heading

git gc prunes unreachable loose objects older than gc.pruneExpire, two weeks by default. Objects packed into a pack file can live longer. Do not count on either; recover as soon as you notice.

Can I recover changes that were never staged? Jump to heading

No. Git only stores content you added to the index or committed. Unstaged edits that were overwritten exist only in editor history, backups or filesystem snapshots.