Skip to content

Fix: Git "cannot lock ref" – Unable to Create Lock File

FixDevs ·

Quick Answer

How to fix the Git error 'cannot lock ref: Unable to create .git/refs/heads/branch-name.lock' caused by stale lock files, case conflicts, packed-refs corruption, and concurrent operations.

The Error

You run git pull, git checkout, or git fetch and get one of these errors:

error: cannot lock ref 'refs/heads/feature-branch': Unable to create
'.git/refs/heads/feature-branch.lock': File exists.

Or a variation like:

error: cannot lock ref 'refs/remotes/origin/main': unable to resolve reference
'refs/remotes/origin/main': reference broken

Sometimes it shows up during git push:

error: cannot lock ref 'refs/heads/main': is at abc1234 but expected def5678

All of these point to the same core problem: Git cannot acquire a lock on a reference file. The good news is that this is almost always fixable without losing any work.

Why This Happens

Git uses lock files to prevent two operations from modifying the same reference at the same time. When you run a command that updates a branch pointer (like git pull or git checkout), Git creates a temporary .lock file in .git/refs/. Once the operation completes, Git removes the lock file automatically.

The “cannot lock ref” error means Git tried to create that lock file but something got in the way. Here are the most common causes:

  • A stale lock file exists. A previous Git operation crashed, was killed, or was interrupted (Ctrl+C during a pull, system crash, IDE timeout). The .lock file was never cleaned up, so Git thinks another process is still using it.
  • Case-sensitivity conflicts. You have branches like Feature/login and feature/login. On macOS and Windows, the filesystem is case-insensitive by default, so these collide. Git’s internal refs treat them as different branches, but the OS cannot create two files with names that differ only in case.
  • Packed-refs corruption. Git periodically packs loose refs into a single .git/packed-refs file for performance. If this file gets corrupted or out of sync with the loose refs, Git cannot resolve the reference.
  • Concurrent Git operations. Two Git processes run at the same time in the same repo. This happens with IDE auto-fetch, background CI scripts, file-sync tools like Dropbox, or running git pull in one terminal while your editor runs git status in another.
  • Stale remote-tracking refs. Remote branches that were deleted on the server still have local tracking refs under .git/refs/remotes/. When Git tries to update these orphaned refs, it can fail with a lock error.
  • File permission issues. The current user does not have write access to the .git/refs/ directory. This is more common on shared servers or when the repo ownership changed. (If you are seeing permission errors across Git more broadly, check out fixing Git permission denied errors.)

Fix 1: Delete the Stale Lock File

This is the most common fix. A leftover .lock file from a crashed operation is blocking Git.

First, confirm no other Git process is running:

# Linux/macOS
ps aux | grep git

# Windows
tasklist | findstr git

If no Git process is active, it is safe to remove the lock file. The error message tells you exactly which file to delete:

rm -f .git/refs/heads/feature-branch.lock

For remote-tracking refs, the path is slightly different:

rm -f .git/refs/remotes/origin/main.lock

You can also remove all stale lock files at once:

find .git -name "*.lock" -type f -delete

Warning: Only run this when you are certain no Git operation is in progress. Deleting a lock file while Git is actively using it can corrupt your repository.

After removing the lock file, retry your command:

git pull

Pro Tip: If you find yourself hitting stale lock files frequently, the root cause is usually something else — an IDE auto-fetching in the background, a flaky network connection dropping during git pull, or a file-sync service like Dropbox or OneDrive interfering with the .git directory. Fix the underlying cause rather than just deleting lock files each time.

Fix 2: Resolve Case-Sensitivity Conflicts

This is the second most common cause, especially on macOS and Windows where the default filesystem is case-insensitive.

Check if you have branches that differ only in case:

git branch -a | sort -f | uniq -di

If you see output like:

feature/login
Feature/Login

These two branches cannot coexist on a case-insensitive filesystem. Git stores branch refs as files on disk, and the OS sees feature/login and Feature/Login as the same path.

Delete the duplicate branch (keep whichever one your team actually uses):

git branch -d Feature/Login

If the branch exists on the remote, delete it there too:

git push origin --delete Feature/Login

Then update your local refs:

git fetch --prune

For remote-tracking refs with case conflicts, you may need to manually clean up the refs directory:

# Remove the conflicting remote ref
rm -rf .git/refs/remotes/origin/Feature
git fetch --prune

Common Mistake: On macOS/Windows, avoid creating branches that differ only in case. A branch named bugfix/JIRA-123 and another named Bugfix/JIRA-123 will inevitably cause lock errors. Establish a naming convention on your team — lowercase with hyphens (like feature/add-login) is the safest choice.

To prevent this in the future, you can enable a Git config option that warns about case issues:

git config --global core.ignoreCase true

This tells Git to acknowledge the case-insensitive filesystem and avoid some of the ref collisions.

Fix 3: Prune Stale Remote-Tracking Refs

When branches are deleted on the remote but your local repo still has tracking refs for them, Git can get confused and throw lock errors during fetch or pull.

Prune all stale remote-tracking branches:

git remote prune origin

Or do it as part of a fetch:

git fetch --prune

To make pruning automatic on every fetch going forward:

git config --global fetch.prune true

This removes local references to remote branches that no longer exist on the server. It does not delete your local branches — only the remote-tracking refs under .git/refs/remotes/.

If pruning alone does not fix it, try a more aggressive cleanup:

git fetch --prune --prune-tags origin

This is especially useful when you have a large number of stale tags causing ref contention.

Fix 4: Run git gc and Repack Refs

Git’s garbage collection process cleans up loose objects and repacks refs. This can fix issues with corrupted or out-of-sync packed refs.

git gc --prune=now

If that does not resolve it, try a more aggressive garbage collection:

git gc --aggressive --prune=now

You can also manually repack the refs:

git pack-refs --all

This moves all loose refs into .git/packed-refs, which can resolve conflicts between loose ref files and the packed-refs file.

If the packed-refs file itself is corrupt, you may need to rebuild it. First, back it up:

cp .git/packed-refs .git/packed-refs.bak

Then delete it and let Git rebuild:

rm .git/packed-refs
git gc --prune=now

Git will regenerate the packed-refs file from the loose refs and objects in the repository.

Fix 5: Fix Broken References Manually

When the error says “unable to resolve reference” or “reference broken,” a ref file exists but contains invalid content.

Check which refs are broken:

git for-each-ref

If this command itself throws errors, the broken ref will be named in the output.

You can also check the ref file directly:

cat .git/refs/remotes/origin/main

A valid ref file contains a single 40-character (SHA-1) or 64-character (SHA-256) commit hash. If the file is empty, contains garbage, or is truncated, the ref is broken.

Delete the broken ref and re-fetch:

rm .git/refs/remotes/origin/main
git fetch origin

For broken local branch refs, you need to know the correct commit hash first:

git reflog show feature-branch

Then recreate the ref:

git update-ref refs/heads/feature-branch <correct-commit-hash>

If the reflog is also missing, check if the branch exists on the remote and recreate it from there:

git fetch origin
git checkout -b feature-branch origin/feature-branch

This situation can sometimes leave you in a detached HEAD state if Git cannot resolve the branch properly. If that happens, create the branch from the correct commit and check it out.

Fix 6: Stop Concurrent Git Operations

If multiple Git processes are competing for the same lock, the fix is to stop them from overlapping.

Check for running Git processes:

# Linux/macOS
ps aux | grep git

# Windows (PowerShell)
Get-Process | Where-Object { $_.ProcessName -like "*git*" }

Common sources of concurrent Git operations:

  • IDE auto-fetch: VS Code, IntelliJ, and other editors periodically run git fetch in the background. Disable auto-fetch in your editor settings if it causes lock conflicts.
  • Git hooks: A post-checkout or post-merge hook that triggers another Git command can create a deadlock. Review your .git/hooks/ directory.
  • File sync services: Dropbox, OneDrive, Google Drive, and iCloud should never sync a .git directory. Exclude it from syncing immediately. These services create lock contention and can corrupt your repo.
  • CI/CD agents: If a build agent shares a workspace across jobs, concurrent builds can collide. Configure your CI to use separate workspaces or add workspace locking.

To disable VS Code’s auto-fetch:

  1. Open Settings (Ctrl+Shift+P > “Preferences: Open Settings (JSON)”)
  2. Add: "git.autoFetch": false

For IntelliJ/WebStorm:

  1. Go to Settings > Version Control > Git
  2. Uncheck “Auto-update if push of the current branch was rejected”

Why this matters: Concurrent Git operations do not just cause lock errors — they can silently corrupt your repository’s object database. If two processes write to the same pack file simultaneously, you can end up with unreachable objects or broken commit chains. Always ensure only one Git process operates on a repo at a time.

Fix 7: Re-clone as a Last Resort

If nothing else works, or if the .git directory is badly corrupted, the fastest path forward is a fresh clone.

First, save any uncommitted work:

# Create a patch of uncommitted changes
git diff > ~/my-changes.patch
git diff --cached > ~/my-staged-changes.patch

If git diff itself fails due to corruption, copy your working tree files manually:

cp -r . ../repo-backup

Then clone fresh:

cd ..
rm -rf my-repo
git clone [email protected]:user/my-repo.git
cd my-repo

Apply your saved changes:

git apply ~/my-changes.patch
git apply ~/my-staged-changes.patch

This gives you a clean .git directory with no stale locks, no corrupt refs, and no case conflicts. It is the nuclear option, but it works every time.

If the original error came from a push rejection rather than a local issue, you may also want to review how to fix non-fast-forward push errors after re-cloning.

Still Not Working?

If you have tried everything above and still see the lock error, check these less obvious causes:

Filesystem-level locks. On Windows, antivirus software can hold file locks on .git directory contents. Temporarily disable real-time scanning for your repo directory, or add an exclusion for .git folders. Windows Defender, Norton, and McAfee are frequent culprits.

Network filesystem issues. If your repo lives on an NFS mount, CIFS share, or other network filesystem, file locking semantics may differ from local storage. Git’s lock mechanism relies on atomic file creation, which some network filesystems do not guarantee. Move the repo to a local disk if possible.

Submodule ref locks. The error might be in a submodule, not the main repo. Check inside each submodule’s .git directory (or .git/modules/<name>/) for stale lock files:

find .git/modules -name "*.lock" -type f

Git worktree conflicts. If you use git worktree, each worktree maintains its own set of refs. A lock in one worktree can block operations in another. Check all linked worktrees:

git worktree list

Remove any stale worktrees:

git worktree prune

Disk full. Git cannot create the lock file if there is no disk space. Check available space:

df -h .

Refs with directory/file conflicts. If you have a branch named feature and another named feature/login, Git cannot create both because feature would need to be both a file and a directory. Delete the conflicting branch:

git branch -d feature

This is similar to the case-sensitivity issue but can happen on any OS, including Linux. If you are dealing with complex merge scenarios alongside these ref issues, see how to resolve merge conflicts for handling the conflict side of things.

Check your Git version. Older Git versions (before 2.20) have known bugs with ref locking. Update to the latest version:

git --version
git update-git-for-windows  # Windows
brew upgrade git             # macOS
sudo apt install git         # Ubuntu/Debian

If your repository is part of a larger infrastructure setup and you are also running into issues initializing repositories, check out fixing “fatal: not a git repository” for related troubleshooting steps.

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