Skip to content

Fix: git fatal: A branch named 'x' already exists

FixDevs · (Updated: )

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.

fatal: A branch named ‘x’ already exists

My reflex the first few times I hit this was git branch -D followed by recreating the branch, and once that reflex deleted a day of commits that existed nowhere else. I learned to treat this error as a question rather than an obstacle: where does the existing ref live, a plain local branch, a packed ref, another worktree, a case-variant on macOS, because the answer decides whether deleting is harmless or destructive. In my experience git reflog show <branch> before any forced delete is the thirty-second habit that makes this error boring instead of dangerous.

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 the related failure when renaming (the source name does not exist, often because you mistyped it or it lives in another worktree):

error: refname refs/heads/feature/login not found
fatal: Branch rename failed

Git refuses to create the branch because a local ref with that name already exists, as an ordinary branch, as a packed ref, or held by a linked worktree. (A remote-tracking ref like origin/feature/login lives in a separate namespace and does not trigger this error on its own.)

Where the Existing Ref Can Hide

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 forgotten local branch shadows the remote one. Seeing remotes/origin/feature/login in git branch -a does not explain the error (remote-tracking refs never block creation); it is the local ref next to it, easy to scroll past, that does.
  • 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, so feature/login cannot exist if feature is 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. The collision happens at the filesystem level where the loose ref files live, so the practical defense is simple: normalize branch names to lowercase across the team.

The other trap is judging branch existence by the .git/refs/heads/ directory. After git pack-refs (which git gc runs for you), existing branches move into the single .git/packed-refs file and their per-branch files disappear. git branch still lists them, the refs machinery reads loose and packed refs alike, but if you were poking at the directory with ls to “see what exists,” it looks empty and the already exists error seems to come from nowhere. Trust git for-each-ref refs/heads/ over a directory listing, and delete a packed branch with git update-ref -d refs/heads/<name> rather than hand-editing packed-refs.

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. Note that remote-tracking refs live in a separate namespace (refs/remotes/) and never block local branch creation, so if the name appears under remotes/origin/ only, plain git branch feature/login would have succeeded. Getting already exists anyway means a local ref really is present, keep going down this list to find where.

Minute 2, Compare against the ref-level view. Run git for-each-ref --format="%(refname) %(objectname:short)" refs/heads/. This is the authoritative list, it reads loose and packed refs alike. If a name shows up here that you could not find by listing .git/refs/heads/, the ref lives in .git/packed-refs; delete it with git update-ref -d refs/heads/<name>.

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, loose ref files live on a case-insensitive filesystem, so a branch named Feature/Login collides with creating feature/login at the file level (this is a filesystem property, core.ignorecase concerns tracked files, not refs). Use git branch -m Feature/Login feature/login to normalize 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/login

Reset it to a different commit (dangerous, rewrites history):

git branch -f feature/login main
# Moves feature/login to point at the same commit as main

Delete 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      # Recreate

Warning: git branch -D deletes 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 stores remote-tracking refs like origin/feature/login under refs/remotes/, a separate namespace that never collides with creating a local branch. So a remote-tracking ref alone cannot produce this error. What you usually want in this situation is either a local branch that tracks the remote one, or cleanup of tracking refs whose remote branch is gone:

Check remote branches:

git fetch --all
git branch -r

Create 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/login

This 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 origin

This 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 main

If 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/login

Fix 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 works

Or rename the existing branch:

git branch -m feature feature-main  # Rename 'feature' to 'feature-main'
git branch feature/login            # Now no conflict

The convention that prevents this class of conflict is worth adopting team-wide: always use two-level names like feature/description, fix/description, chore/description, and never create a bare category name like feature or fix as a standalone branch. One bare feature branch blocks every future feature/* name in the repository, and the person who hits the conflict is usually not the person who created it.

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-name

If a branch with new-name already exists, use -M (force):

git branch -M old-name new-name

After 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-name

For 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/login

After manual cleanup, run git gc to garbage-collect loose objects:

git gc --prune=now

For packed refs (branches stored in .git/packed-refs instead of individual files):

cat .git/packed-refs | grep feature/login
# If it appears there, delete it with update-ref — do NOT hand-edit the file,
# a malformed packed-refs can corrupt every ref in the repository:
git update-ref -d refs/heads/feature/login

When the Branch Still Blocks You

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/
# login.lock  ← leftover from an interrupted operation on feature/login

rm .git/refs/heads/feature/login.lock

A stale lock fails with cannot lock ref ... File exists plus a hint about another git process; once you have confirmed no other git process is running, Git’s own advice is the fix, remove the file manually.

Check your Git version. Very old versions of Git have bugs with ref management. Run git --version and update if you are below 2.30.

Don’t judge branch existence by ls .git/refs/heads/. After git pack-refs or git gc, branch files move into .git/packed-refs and the directory can look empty even though every branch still exists (and git branch still lists them). git for-each-ref refs/heads/ is the authoritative view; git update-ref -d refs/heads/name deletes 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles