Photo by Pankaj Patel on Unsplash
So I was doing a routine git fetch at like 11pm trying to sync up before a release, and boom — this cryptic error just stares back at me:
error: cannot lock ref 'refs/remotes/origin/main':
'refs/remotes/origin/main/feature-branch' exists;
cannot create 'refs/remotes/origin/main'
I'd seen git do weird things before, but this one had me genuinely stumped for a few minutes. A git remote tracking refs conflict is one of those errors that feels more confusing than it actually is. Once you understand what's going on under the hood, the fix is pretty straightforward — but if you don't know where to look, you'll be Googling for an hour.
Let me break down what's actually happening here and how to get past it.
Why This Happens in the First Place
Git stores information about remote branches in your local .git/refs/remotes/ directory. Each remote branch gets its own file path. The problem comes when someone on your team (or even you, in a past session) created a branch like origin/main/feature-branch. Git tried to store that as a file at refs/remotes/origin/main/feature-branch — which means it created main as a directory.
Now when Git tries to create a tracking ref for origin/main (the actual branch), it can't — because main already exists as a folder on disk. You can't have a file and a directory with the same name. It's a filesystem-level conflict, not really a Git logic issue. Annoying, right?
This comes up a lot when teams rename branches, delete old branches, or when someone creates branches with names that contain slashes in a way that mirrors an existing branch's name. I've seen this trip up even senior devs who've been using Git for years.
Fix #1: Delete the Stale Remote Tracking Refs
This is the first thing to try and works 90% of the time. The conflicting ref is usually a leftover ghost — a branch that got deleted on the remote but the reference is still hanging around locally.
git remote prune origin
This tells Git to clean up any remote-tracking references that no longer exist on the remote. After running this, try your fetch again:
git fetch origin
Honestly, this single command has saved me more times than I can count. If the offending branch was already deleted on the remote, pruning will nuke the stale ref and everything goes back to normal.
You can also combine fetch and prune in one shot going forward to avoid this building up again:
git fetch --prune origin
Worth adding that to your muscle memory or even setting it as default behavior in your git config (more on that at the end).
Fix #2: Manually Delete the Conflicting Ref
Sometimes pruning doesn't cut it — especially if the conflicting branch still exists on the remote or the ref is just corrupted locally. In that case, you need to go in and remove it yourself.
First, figure out exactly which ref is causing the problem. The error message usually tells you — look for the path after "exists;":
ls .git/refs/remotes/origin/
If you spot something that looks like it should be a file but is showing up as a directory (like main/ when you expect main as a file), that's your culprit. Delete it:
rm -rf .git/refs/remotes/origin/main
Be careful with that rm -rf — only target the specific path causing the issue. Don't go deleting things blindly in your .git folder. Now try the fetch again and it should work.
Actually, a slightly safer way to do this is through the git command itself:
git update-ref -d refs/remotes/origin/main/feature-branch
This deletes just the specific stale ref without you having to touch the filesystem directly. I prefer this approach when I'm not 100% sure what else might be living in that directory.
Fix #3: Use git pack-refs and Repack
Here's the thing though — sometimes your refs can also live in a packed format inside .git/packed-refs, not as individual files. If the conflict is buried in there, the manual file deletion won't do anything.
Open up .git/packed-refs in any text editor and look for lines that reference the conflicting branch:
cat .git/packed-refs
You'll see something like:
abc1234def5678 refs/remotes/origin/main/feature-branch
Manually delete that line, save the file, then run:
git pack-refs --all
This repacks all your refs cleanly. Then try the fetch. This is admittedly the most manual approach, but it works when the other two don't — and I've had to do it maybe twice over the years when things were particularly messy.
Fallback: Full Remote Reset (Nuclear Option)
If none of the above work and you're dealing with a really mangled state — maybe after a botched migration or a team-wide branch rename gone wrong — you can just wipe and re-initialize your remote tracking refs entirely.
rm -rf .git/refs/remotes/origin
rm -f .git/packed-refs
git fetch origin
This is the scorched earth approach. You're deleting all local knowledge of remote branches and letting git rebuild it from scratch on the next fetch. Your actual local branches are untouched — this only affects the remote tracking data. I wouldn't lead with this one, but if you're stuck and the release is in two hours, it gets the job done.
Prevent This From Happening Again
Set prune to run automatically every time you fetch. Just add this to your global git config:
git config --global fetch.prune true
It's one of those settings that should honestly be on by default. Once you enable it, stale remote tracking branches get cleaned up automatically and you're way less likely to run into this kind of namespace conflict down the road.
Also — and this is more of a team process thing — try to establish a naming convention that avoids slash-heavy branch names that mirror existing branch names. Something like main-feature-branch instead of main/feature-branch. Boring advice, I know, but it prevents a whole category of these headaches.
Hope this saves you the 45 minutes I burned the first time I hit this. The error looks scary but it's really just Git getting confused by its own filing system — clean up the stale refs and you're good to go.
Related: How to Fix "git error: cannot lock ref" (And Why It Keeps Coming Back)
๋๊ธ
๋๊ธ ์ฐ๊ธฐ