Photo by Laine Cooper on Unsplash
So I was doing a routine git fetch on a project I hadn't touched in a few weeks, and boom — the terminal just spits out this mess:
error: cannot lock ref 'refs/remotes/origin/feature/login':
'refs/remotes/origin/feature' exists; cannot create 'refs/remotes/origin/feature/login'
fatal: fetch failed
Nothing else. No helpful hint. Just Git being Git. I've seen this error probably a hundred times across different teams and machines, and every single time it catches someone off guard. The good news is it's almost always fixable in under five minutes once you know what's actually going on.
Why This Even Happens
Here's the thing — Git stores your remote-tracking refs as actual files in .git/refs/remotes/. So when you have a branch called origin/feature/login, Git tries to create a file at that path. But if there's already a file (not a folder) named feature sitting there — maybe from an old branch that used to exist with that exact name — Git can't also treat feature as a directory. Your filesystem won't allow a path to be both a file and a folder at the same time. Git hits that wall and just gives up.
This comes up a lot in team environments where branch naming conventions change over time. Someone had a branch called feature, it got merged and deleted from the remote, but the stale ref is still hanging around locally in your repo. Then a new branch comes along named feature/login and suddenly Git is confused.
It can also happen after a corrupted fetch, a hard reset gone wrong, or sometimes just bad luck with parallel Git operations. Honestly, I've seen it appear out of nowhere on repos that no one touched for a while.
Fix #1: Prune Your Stale Remote Refs
This is the first thing I try, and it fixes the issue probably 70% of the time. Git has a built-in way to clean up refs that no longer exist on the remote:
git fetch --prune origin
What --prune does is delete any local remote-tracking refs that have been deleted from the remote. So if origin/feature doesn't exist anymore, it gets cleaned out — and then Git can happily create origin/feature/login as a proper directory path.
If you want to make this automatic going forward (and honestly you should), you can set it globally so every fetch prunes by default:
git config --global fetch.prune true
Set it and forget it. Saves a lot of headaches down the line.
Fix #2: Manually Delete the Conflicting Ref
Sometimes pruning doesn't catch everything — especially if the conflicting ref isn't actually a remote-tracking ref but something that got mangled locally. In that case, you need to go in and delete it yourself.
First, figure out exactly what's conflicting. The error message usually tells you — in the example above it's refs/remotes/origin/feature. You can delete it with:
git update-ref -d refs/remotes/origin/feature
Or if that doesn't work (sometimes Git is weirdly protective about these things), just go straight to the filesystem:
# On Mac/Linux
rm .git/refs/remotes/origin/feature
# On Windows (Git Bash)
rm -f .git/refs/remotes/origin/feature
After that, run your fetch again and it should go through clean. Now here's where it gets tricky — if the ref is packed (meaning it lives in .git/packed-refs instead of as its own file), you won't find it in the refs/ folder at all. Open up .git/packed-refs in a text editor and look for the offending ref, then just delete that line. Save the file and try again.
Fix #3: Run git fsck and Clean Up Loose Objects
If the above two fixes didn't work — or if you're seeing this error alongside other weird Git behavior — there might be deeper ref corruption going on. This is the less common scenario, but worth checking.
git fsck --full
This runs a full integrity check on your repository. Look through the output for anything flagged as broken link, dangling, or missing. If you see a bunch of those, your ref database might be partially corrupted.
A good follow-up is running Git's garbage collection, which cleans up loose objects and repacks refs in a healthier state:
git gc --aggressive --prune=now
Fair warning — --aggressive can take a few minutes on large repos. Grab a coffee. It's worth it though, especially if the repo has been accumulating cruft for months.
Fallback: Just Re-clone the Repo
I know, I know — nobody wants to hear this. But sometimes the local repo state is just too far gone and spending another hour debugging it costs more than a fresh clone. If nothing above worked and the remote is healthy, this is your nuclear option:
cd ..
git clone project-fresh
cd project-fresh
Before you do this, make sure you don't have any uncommitted local changes you'll lose. Run git stash or copy out anything important first. Also double-check your local branches — any branch that only exists locally won't be on the remote, so either push those up or patch them in manually after the re-clone.
(Side note: if you're on a massive monorepo where a full clone takes 20 minutes, look into git clone --depth 1 for a shallow clone to at least get you unblocked quickly.)
Quick Recap
The cannot lock ref error almost always comes down to stale or conflicting ref files in your local .git directory. Start with git fetch --prune, move to manual deletion of the specific conflicting ref if needed, and run git fsck if things look more seriously corrupted. And just turn on fetch.prune true globally — future you will thank present you.
Hope this saves you from a debugging spiral at the wrong time of day.
๋๊ธ
๋๊ธ ์ฐ๊ธฐ