Fix: git fatal: A branch named 'x' already exists
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix 'git fatal: A branch named already exists' when creating or renaming branches — including local conflicts, remote tracking branches, and worktree issues.
The Error
You try to create or rename a Git branch and get:
fatal: A branch named 'feature/login' already exists.Or when switching with branch creation:
fatal: A branch named 'main' already exists.Or when renaming:
error: refname refs/heads/feature/login not found
fatal: Branch rename failedGit refuses to create the branch because a branch with that name already exists — locally, as a remote-tracking reference, or in a linked worktree.
Why This Happens
Git stores branches as references (refs) in .git/refs/heads/. When you run git branch feature/login, Git tries to create a new ref at that path. If one already exists, it fails. Common causes:
- You already created this branch in a previous session and forgot.
- A remote-tracking branch exists with the same name (e.g.,
origin/feature/loginalready fetched). - A Git worktree has this branch checked out — a branch cannot be checked out in two worktrees simultaneously.
- A partial branch name conflict — Git uses
/as a directory separator in refs, sofeature/logincannot exist iffeatureis already a file (not a directory) in.git/refs/heads/. - Corrupted or leftover refs from a failed operation.
There is a sixth cause that bites teams working across platforms: case sensitivity. On Linux, Feature/Login and feature/login are two different refs and Git is happy to host both. On macOS APFS (case-insensitive by default) and Windows NTFS, they collide at the filesystem layer. A clone done on Linux can leave a repo in a state that no macOS or Windows machine can fully check out, and the symptom is branch already exists when you try to create one of the conflicting names. Always normalize branch names to lowercase, and run git config core.ignorecase to see how your local Git is interpreting filenames.
The other rarely-named cause is stale reflog and packed-refs. After a git gc, branches that you thought you deleted may still appear in .git/packed-refs as a single line entry. The branch is invisible to git branch (which reads from .git/refs/heads/) but visible to git for-each-ref, and creation fails because Git’s ref-lookup hits the packed entry first. Run git for-each-ref refs/heads/ to see what Git really thinks is there.
Diagnostic Timeline
The first instinct is git branch -D feature/login && git branch feature/login. That works if the branch is just a leftover, but it destroys commits if you guessed wrong. Walk through this list first.
Minute 0 — List everything Git knows about the name. Run git branch -a --list "*feature/login*". The -a shows local plus remote-tracking. If it appears under remotes/origin/ only, the local branch does not actually exist — you tried to create a local with the same name as a remote-tracking ref and Git complained about ambiguity.
Minute 2 — Compare against the ref-level view. Run git for-each-ref --format="%(refname) %(objectname:short)" refs/heads/. This shows packed refs that git branch may hide. If the name appears here but not in git branch, the ref is stuck in .git/packed-refs and needs git update-ref -d.
Minute 4 — Check worktree ownership. Run git worktree list. If the branch is checked out in another worktree (often a forgotten one in /tmp or a sibling directory), Git refuses to let any operation touch it. Either cd into that worktree, or run git worktree remove <path> to release the ref.
Minute 6 — Look for a lock file. Run ls .git/refs/heads/ and search for any *.lock. A previous interrupted git branch leaves these behind and they block all writes to the branch. rm .git/refs/heads/feature/login.lock clears it.
Minute 8 — Verify case. On macOS or Windows, run git config core.ignorecase. If it is true, Git is doing case-insensitive lookups. A branch named Feature/Login blocks creation of feature/login. Use git branch -m Feature/Login feature/login to fix the case.
Minute 10 — Inspect packed-refs. cat .git/packed-refs | grep feature/login. If you see an entry, the branch is packed. Run git update-ref -d refs/heads/feature/login to delete it cleanly without editing the file.
Minute 12 — Check the reflog. git reflog show feature/login. If commits exist here that are not on any other branch, you are about to delete unique work. Cherry-pick them somewhere safe before running -D.
Minute 15 — Decide intent. “I want to recreate from a different base” → git checkout -B feature/login main. “I want a fresh branch” → delete the old one only after confirming the reflog is empty or recoverable. “I want to push to a remote name that is already taken” → push to a new name and rename the remote later.
Fix 1: Check If the Branch Already Exists
Before creating, verify what branches exist:
# List all local branches
git branch
# List all remote-tracking branches
git branch -r
# List all branches (local + remote)
git branch -a
# Search for a specific branch name
git branch -a | grep "feature/login"If the branch already exists locally, you have three options:
Switch to it:
git checkout feature/login
# or
git switch feature/loginReset it to a different commit (dangerous — rewrites history):
git branch -f feature/login main
# Moves feature/login to point at the same commit as mainDelete it and recreate:
git branch -d feature/login # Safe delete (fails if unmerged)
git branch -D feature/login # Force delete (even if unmerged)
git branch feature/login # RecreateWarning:
git branch -Ddeletes the branch and discards any commits that exist only on that branch. Make sure you do not need those commits, or cherry-pick them to another branch first.
Fix 2: Create the Branch from a Different Starting Point
If you want to reset an existing branch to start fresh from a different commit:
# Reset the branch to point to main's current commit
git branch -f feature/login main
# Or to a specific commit
git branch -f feature/login abc1234
# Then switch to it
git switch feature/login-f (force) moves the branch pointer even if it already exists. This does not delete any commits — it just moves the label.
Alternatively, use checkout -B:
git checkout -B feature/login main-B (uppercase) creates the branch if it does not exist, or resets it to the given start point if it does. It also switches to the branch in one command.
Fix 3: Fix Remote-Tracking Branch Conflicts
When you fetch from a remote, Git creates remote-tracking branches like origin/feature/login. These do not conflict with local branches by default — but the error can appear if you try to create a local branch with the exact same full ref path as a remote-tracking branch, or if the remote branch was set up in a way that creates a naming conflict.
Check remote branches:
git fetch --all
git branch -rCreate a local branch that tracks the remote branch:
git checkout -b feature/login origin/feature/login
# or
git switch -c feature/login --track origin/feature/loginThis creates a local feature/login that tracks origin/feature/login. If a local branch with that name already exists, use git branch -f to reset it first.
If the remote branch no longer exists but the tracking reference persists:
# Prune stale remote-tracking references
git fetch --prune
git remote prune originThis removes remote-tracking branches that no longer exist on the remote.
Fix 4: Fix Worktree Branch Conflicts
If you use git worktree, a branch can only be checked out in one worktree at a time. Trying to check it out in a second worktree fails:
git worktree add ../new-worktree feature/login
# fatal: 'feature/login' is already checked out at '/path/to/original-worktree'Fix — list and manage worktrees:
# See all worktrees and their branches
git worktree list
# Remove a worktree you no longer need
git worktree remove ../old-worktree
# Or create a new worktree with a new branch
git worktree add ../new-worktree -b feature/login-v2 mainIf a worktree was deleted without git worktree remove (e.g., you deleted the folder manually), the ref may be locked:
# Clean up stale worktree references
git worktree prune
# Then try the branch operation again
git worktree add ../new-worktree feature/loginFix 5: Fix Branch Naming Conflicts with Slashes
Git uses / in branch names as a path separator in the refs filesystem. This means you cannot have both a branch named feature and a branch named feature/login — feature would need to be both a file and a directory in .git/refs/heads/.
Broken — naming conflict:
git branch feature # Creates .git/refs/heads/feature (a file)
git branch feature/login # Tries to create .git/refs/heads/feature/login
# fatal: cannot lock ref 'refs/heads/feature/login':
# 'refs/heads/feature' exists; cannot create 'refs/heads/feature/login'Fix — delete the conflicting branch:
git branch -d feature # Delete the 'feature' branch
git branch feature/login # Now this worksOr rename the existing branch:
git branch -m feature feature-main # Rename 'feature' to 'feature-main'
git branch feature/login # Now no conflictPro Tip: Use a consistent naming convention for branches. Common patterns:
feature/description,fix/description,chore/description. Avoid creating bare category names likefeatureorfixas standalone branches — they will conflict with all branches in that category.
Fix 6: Rename a Branch
To rename a local branch:
# Rename the current branch
git branch -m new-name
# Rename a specific branch (not currently checked out)
git branch -m old-name new-nameIf a branch with new-name already exists, use -M (force):
git branch -M old-name new-nameAfter renaming, update the remote:
# Push the new name
git push origin new-name
# Delete the old name from remote
git push origin --delete old-name
# Update the tracking reference
git branch --set-upstream-to=origin/new-name new-nameFor the main/master rename specifically, see GitHub’s branch rename guide — it involves additional steps for open pull requests and branch protection rules.
Fix 7: Clean Up Leftover or Corrupted Refs
After failed operations, corrupted refs can leave branches in a broken state:
# Check for broken refs
git fsck --full
# View the raw ref to see what it points to
cat .git/refs/heads/feature/login
# Manually delete a broken ref file
rm .git/refs/heads/feature/login
# Or use git update-ref to delete it cleanly
git update-ref -d refs/heads/feature/loginAfter manual cleanup, run git gc to garbage-collect loose objects:
git gc --prune=nowFor packed refs (branches stored in .git/packed-refs instead of individual files):
cat .git/packed-refs | grep feature/login
# If it appears there, edit the file manually to remove that line
# Or use git update-ref:
git update-ref -d refs/heads/feature/loginStill Not Working?
Check for case sensitivity issues. On macOS and Windows (case-insensitive filesystems), Feature/Login and feature/login are the same file. Git may warn about this or fail silently. Use all-lowercase branch names to avoid this entirely.
Check for locked ref files. If a previous Git operation was interrupted, it may have left a .lock file:
ls .git/refs/heads/feature/
# feature.lock ← This is leftover from a failed operation
rm .git/refs/heads/feature/login.lockCheck your Git version. Very old versions of Git have bugs with ref management. Run git --version and update if you are below 2.30.
Inspect git for-each-ref for packed refs. git branch reads only loose refs and misses entries in .git/packed-refs. Run git for-each-ref refs/heads/ to see every branch Git actually knows about, then git update-ref -d refs/heads/name to delete a packed branch cleanly without hand-editing the file.
Check for stale worktree administrative directories. If a worktree was deleted without git worktree remove, the entry in .git/worktrees/<name> still claims the branch. Run git worktree prune --verbose and confirm the branch is released before retrying creation.
Look for cannot lock ref errors mixed in with the message. That variant has its own playbook around long-running fetch processes and file permissions on .git/refs/. Check for stale .lock files before deleting them manually, and confirm no other Git process holds the ref.
Check submodule branch conflicts. A submodule may have its own copy of a branch with the same name. The error originates in the submodule’s .git directory, not the parent. git -C path/to/submodule branch -a reveals the real conflict.
For errors pushing branches after creating them, see Fix: git push rejected (non-fast-forward) and Fix: git error failed to push some refs.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Undo git reset --hard and Recover Lost Commits
How to undo git reset --hard and recover lost commits using git reflog — step-by-step recovery for accidentally reset branches, lost work, and dropped stashes.
Fix: .gitignore Not Working (Files Still Being Tracked)
How to fix .gitignore not working — files still showing in git status after being added to .gitignore, caused by already-tracked files, wrong syntax, nested gitignore rules, and cache issues.
Fix: Git Worktree Not Working — Branch Already Checked Out, Prune, Submodules, and Locked Worktrees
How to fix git worktree errors — fatal: 'branch' is already checked out at, worktree prune removing valid trees, detached HEAD on add, submodules not initialized, moving/locking worktrees, and ignoring per-worktree files.
Fix: DVC Not Working — Remote Push Errors, Pipeline DAG Issues, and Git Integration
How to fix DVC errors — dvc push authentication failed, dvc pull file missing, pipeline stage not reproducing, cache out of disk space, dvc add vs dvc stage, conflict with git LFS, and S3/GCS remote setup.