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 --hardorgit checkout -- .and lost them. - A stash was dropped or cleared, and
git stash listno 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 pruneorgit maintenance runwhile recovering. Each can permanently delete the unreachable objects you are looking for. Restoregc.auto(git config --unset gc.auto) only after you have rescued everything you need.
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 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.
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.
Related Jump to heading
- History Rewriting & Recovery β the parent topic.
- Recovering a Deleted Branch β the case where a ref-based approach usually suffices.
- Scheduling Git Maintenance for Background Repacking β the maintenance you pause during recovery.
- Stash vs Worktree vs WIP Commit β ways of parking work that are easier to recover.