Quick Summary
- Broken Git refs usually happen after a crash, bad merge, or botched rebase that leaves your .git folder in a half-written state
- You've got a few ways to fix it: pack-refs cleanup, manual ref deletion, or a full fsck with prune
- Most of the time you can recover without losing commits, but you need to act before you push more garbage on top of the broken state
Photo by Cong Long Vu on Unsplash
It was a Friday afternoon. Of course it was. I'd just force-pushed a branch cleanup script I'd half-tested on a repo with about 60 contributors, and somewhere in the middle of it, my laptop lost power. When it came back up and I tried to run git status, the terminal just sat there for a second and then vomited a wall of errors I hadn't seen before. "error: bad ref for .git/refs/remotes/origin/main" — and like fifteen more lines just like it. The whole repo felt broken. Pushing failed. Fetching failed. Even switching branches was throwing fits.
If you've landed here, you probably did something similar. Maybe it wasn't a power cut — maybe it was a script gone wrong, a hard reset mid-operation, or you cloned something over a flaky connection and didn't notice until later. Either way, the repo is now in a state where Git doesn't fully trust its own internal bookkeeping, and it's refusing to cooperate. Annoying as it is, this is actually fixable most of the time. Here's what's happening and how to get out of it.
What the Error Actually Looks Like
$ git fetch origin
error: bad ref for .git/refs/remotes/origin/main
error: bad ref for .git/refs/remotes/origin/feature-login
fatal: pack_refs failed
$ git fsck
Checking object directories: 100% (256/256), done.
error: HEAD: invalid sha1 pointer af324b...
error: bad ref for refs/heads/develop
dangling blob a1c3f9d8e2...
missing blob 3f9a1c...
You might see some of those, all of those, or a slightly different variation depending on exactly how things broke. The core problem is the same: Git's ref files — the tiny text files that point your branch names to commit hashes — are empty, corrupt, or contain data that doesn't match any real object in the repo.
Photo by Ilija Boshkov on Unsplash
Why This Happens
Git writes ref files atomically — or it tries to. When you switch a branch or update a remote tracking ref, it writes to a temp file and then renames it into place. If that write gets interrupted (crash, kill signal, disk full, whatever), you can end up with a ref file that's empty or only partially written. Git then opens it, sees garbage or nothing, and panics.
The other common cause is the packed-refs file getting out of sync. Git can store refs in two places: loose files under .git/refs/ or a single packed file at .git/packed-refs. If both have conflicting or malformed entries for the same ref, you're going to have a bad day. I've seen this happen specifically after a git gc ran mid-clone on a slow server. Not fun.
And honestly, I've also seen it from people running git remote prune origin while a fetch was already in progress in another terminal window. Race conditions are real even on a single machine.
Photo by Jake Walker on Unsplash
Fix 1: Clean Up Broken Loose Refs Manually
This is the first thing I try. Go look at the actual ref files Git is complaining about.
# Check what's in the broken ref
cat .git/refs/remotes/origin/main
# If it's empty or shows garbage, delete it
rm .git/refs/remotes/origin/main
# Then re-fetch to regenerate it
git fetch origin
Sounds almost too simple. But half the time, that's literally it. The ref file is just empty — zero bytes — and deleting it lets Git fetch a fresh correct version from the remote. Check the file first before you delete it. If it contains a valid 40-character SHA, don't delete it; the problem is somewhere else.
After cleaning up loose refs, run this to re-pack everything cleanly:
git pack-refs --all --prune
This consolidates all your loose refs into the packed-refs file and prunes the ones that don't have corresponding objects. I run this as cleanup even when things aren't broken, honestly. It's just good hygiene.
Photo by Bernd ๐ท Dittrich on Unsplash
Fix 2: Run a Full fsck and Prune
If the first fix didn't sort it, or you've got missing objects on top of the broken refs, you need to go deeper.
# Full integrity check
git fsck --full
# Remove unreachable objects
git prune
# Then repack everything
git gc --aggressive
The git fsck --full output is going to look scary. Don't panic at "dangling blob" or "dangling commit" — those are usually just orphaned objects from rebases or old branches and they're harmless. The things you actually care about are "missing blob" or "missing tree" errors, because those mean a commit you care about references an object that doesn't exist on disk.
If you see missing objects, stop before you prune. Those objects might be recoverable from a remote. Run this first:
git fetch --all
git fsck --full
Fetch first, then check again. A lot of "missing" objects turn out to just not have been downloaded yet.
Fix 3: Rebuild the HEAD and Packed-Refs From Scratch
This is the nuclear option but I've had to use it. If your HEAD is corrupted and pointing at nothing, Git can't even tell what branch you're on.
# Check what HEAD says
cat .git/HEAD
# It should say something like: ref: refs/heads/main
# If it's empty or has a bad SHA, fix it manually
echo "ref: refs/heads/main" > .git/HEAD
Then rebuild packed-refs by deleting it and letting Git recreate it:
rm .git/packed-refs
git fetch --all
git pack-refs --all
Yes, deleting packed-refs feels terrifying. But if it's corrupt, it's already causing problems, and Git can rebuild it from remote tracking data and your remaining loose refs. Back up your .git folder first if you're nervous — just cp -r .git .git-backup takes about five seconds and will save your sanity if something goes wrong.
Fallback: Clone Fresh and Transplant Your Unpushed Work
Sometimes the repo is just too far gone. If fsck is showing missing trees and missing commits and nothing above is working, the fastest path forward is a fresh clone.
git clone git@github.com:yourorg/yourrepo.git repo-fresh
cd repo-fresh
git checkout -b my-recovered-branch
Then go back to the broken repo, identify any commits you haven't pushed yet using git log --oneline origin/main..HEAD, and cherry-pick them over. It's annoying but it works, and you lose maybe 20 minutes instead of 4 hours of trying to surgery a broken repo back to health.
Look, broken refs are one of those things that feel catastrophic the first time you see them and kind of boring the fifth time. The data is almost always still there — Git is just confused about where things are. Take a breath, back up your .git directory, and work through the fixes in order. You'll get it back. And maybe invest in a UPS if power cuts are a regular thing in your office.
Practical IT troubleshooting, Zoho Workplace guides, health insights, and sports analysis from people who actually care about getting it right. We research, test, and write so you get real answers — not recycled fluff.
๋๊ธ
๋๊ธ ์ฐ๊ธฐ