Fix: Git Worktree Not Working — Branch Already Checked Out, Prune, Submodules, and Locked Worktrees
Quick Answer
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.
The Error
You try to add a worktree for a branch and git refuses:
$ git worktree add ../feature-x feature-x
fatal: 'feature-x' is already checked out at '/path/to/main/repo'Or you create a worktree and end up on a detached HEAD:
$ git worktree add ../experiment
Preparing worktree (detached HEAD abc1234)
HEAD is now at abc1234 chore: bump depsOr git worktree prune deletes worktrees you still need:
$ git worktree list
/path/to/main abc1234 [main]
/path/to/feature-x def5678 [feature-x]
$ git worktree prune
$ git worktree list
/path/to/main abc1234 [main]
# feature-x gone — but the directory still exists with uncommitted work!Or submodules show up empty in the new worktree:
$ git worktree add ../feature-x feature-x
$ cd ../feature-x/vendor/lib
$ ls
# Empty.Why This Happens
A worktree is a second working directory linked to the same .git repository. The main repo holds the objects database and the worktree-specific metadata sits in .git/worktrees/<name>/. Most failures come from:
- Branches are exclusive across worktrees. A branch can only be checked out in one place at a time. The main repo and every worktree must each be on a different branch (or one of them on a detached HEAD).
worktree pruneremoves “stale” worktrees. A worktree is “stale” if its directory no longer exists at the recorded path. If you moved the directory by hand (withoutgit worktree move), git thinks it’s gone and prunes the metadata.- Submodules are per-checkout. A new worktree doesn’t auto-init submodules. You need
git submodule update --init --recursivein each worktree. gitignoreis global but per-worktree state isn’t. Files like.envneed to exist in each worktree if your build expects them.
Fix 1: Detach or Use a New Branch
If a branch is checked out in the main repo, you can’t git worktree add it elsewhere. Two solutions:
Switch the main repo to a different branch first:
# In main repo
git switch main
# Now create the worktree
git worktree add ../feature-x feature-xCreate a new branch for the worktree:
git worktree add -b feature-x ../feature-x
# Creates a new branch 'feature-x' from the current HEAD, checked out in the worktree.To base it off a specific ref:
git worktree add -b feature-x ../feature-x main
# 'feature-x' branches from 'main'.Pro Tip: Adopt the convention: never check out feature branches in the main repo — keep main checked out there, and use worktrees for every other branch. You’ll stop hitting the “already checked out” error entirely.
Fix 2: Avoid Accidental Detached HEAD
git worktree add ../experiment (no branch given) checks out the current HEAD as detached. Commits there aren’t on any branch and are easy to lose.
Always pass a branch name:
# Existing branch:
git worktree add ../feature-x feature-x
# New branch from current HEAD:
git worktree add -b feature-x ../feature-x
# New branch from a specific ref:
git worktree add -b hotfix ../hotfix origin/release/v2If you do end up detached and made commits, save them before switching:
# Inside the detached worktree
git switch -c saved-work # Creates a branch pointing at HEAD.Fix 3: Move Worktrees With git worktree move, Not mv
Manually moving a worktree directory breaks the link:
mv ../feature-x ../old-feature-x # WRONG
# Now `git worktree list` shows the old path; prune removes it.Use the git command:
git worktree move ../feature-x ../old-feature-xThis updates .git/worktrees/feature-x/gitdir to the new path and the linked .git file inside the worktree.
If you already moved manually, recover with git worktree repair:
# After a manual mv
cd ../old-feature-x
git worktree repair
# Or from the main repo:
git worktree repair ../old-feature-xrepair updates the metadata to point at the actual filesystem location. Works for both renamed and moved worktrees.
Fix 4: Don’t Prune Until Listing Looks Right
git worktree prune removes metadata for worktrees whose directories are missing. Before pruning, check the list:
git worktree list
# Look for any entry marked 'prunable':
git worktree list -vIf a real worktree shows as prunable, the directory got renamed or moved. Either git worktree move it back (or to its actual location) or run git worktree repair first.
To prune only worktrees that have been “missing” for a while:
git worktree prune --expire=7.days.agoCombined with regular --dry-run checks, this is the safe pattern:
git worktree prune --dry-run
# Review the output.
git worktree pruneCommon Mistake: Running git gc --aggressive while worktrees hold uncommitted changes or unmerged refs. Modern git is worktree-aware (it considers each worktree’s HEAD and index reachable), but if your worktree directory is renamed and shows as prunable, gc may sweep objects it references. Always check git worktree list -v for prunable entries and run git worktree repair before any aggressive cleanup.
Fix 5: Lock Worktrees on Slow Storage
If a worktree lives on an external drive or network mount that comes and goes, git may prune it when the drive is unmounted. Lock it:
git worktree lock ../on-external-drive --reason "Used for backups; do not prune"Locked worktrees show with locked in git worktree list -v and survive prune. Unlock when ready:
git worktree unlock ../on-external-driveLock is also useful for worktrees that hold long-running test environments — you don’t want a colleague’s git worktree prune killing your in-flight CI run.
Fix 6: Init Submodules in Each Worktree
Submodules aren’t auto-checked-out in new worktrees:
git worktree add ../feature-x feature-x
cd ../feature-x
git submodule update --init --recursiveIf you do this often, add a hook or a script:
# scripts/new-worktree.sh
#!/usr/bin/env bash
set -e
git worktree add "../$1" "$1"
cd "../$1"
git submodule update --init --recursive
cp ../main-repo/.env .env # Copy env if needed../scripts/new-worktree.sh feature-xNote: Submodules added by URL hit the network on each update --init. For frequent worktree creation, set up a local mirror or git config submodule.<name>.url to a local path.
Fix 7: .gitignored Files Don’t Carry Over
.env, node_modules, .venv — anything .gitignored isn’t materialized in new worktrees. The build will fail with “missing dependency” until you re-create or copy them.
A safe pattern:
git worktree add ../feature-x feature-x
cd ../feature-x
# Re-create per-worktree state:
cp ../main-repo/.env.local .env.local
npm install # or pnpm install / cargo build / etc.For monorepos with many ignored artifacts, link them once with a script. Don’t symlink node_modules between worktrees if your dependencies vary by branch — the worktree using stale deps will silently use the wrong versions.
Pro Tip: direnv (.envrc) survives across worktrees because the file is committed. Use direnv for per-project env vars instead of un-versioned .env files when possible.
Fix 8: Branch Operations Across Worktrees
A few branch commands respect worktree state:
# Delete a branch — fails if checked out in any worktree:
git branch -d feature-x
# error: cannot delete branch 'feature-x' checked out at '../feature-x'
# Remove the worktree first:
git worktree remove ../feature-x
git branch -d feature-x
# Or delete the worktree and the branch together:
git worktree remove ../feature-x
git branch -D feature-x # Force delete if not merged.To rename a branch that’s checked out in a worktree:
# This works — git updates the worktree's HEAD too:
git branch -m old-name new-nameCommon Mistake: Editing .git/worktrees/<name>/HEAD directly to “fix” a stuck worktree. This bypasses git’s locking and can corrupt the worktree. Use git worktree remove --force and re-add instead.
Still Not Working?
A few less-obvious failures:
fatal: not a git repositoryinside a worktree. The link file.gitinside the worktree directory points at.git/worktrees/<name>in the main repo. If you copied the worktree without git’s help, the path is wrong. Rungit worktree repairfrom the main repo with the worktree’s path.- Worktree on a different filesystem fails to checkout. Git uses hardlinks to share objects between worktrees on the same filesystem. Across filesystems, it copies. This is slower but works — disable with
git config core.worktreeConfig trueand configure per-worktree. git pullin worktree A updates branch checked out in worktree B. That’s correct — both worktrees share the same branch refs. After the pull, worktree B’s working directory may be out of sync.git statuswill show it. Have B rungit pull(orgit reset --hard) to catch up.git fetch --allis slow with many worktrees. Worktrees share remotes; the fetch is once per remote, not once per worktree. Slow fetch = network/server, not worktree count.git stashin a worktree applies in a different worktree. Stashes are global, indexed by stash ID. Usegit stash listandgit stash pop stash@{N}explicitly to apply the right one.pre-commit/huskyhooks don’t run. Each worktree has its own.gitlink, not a.git/hooksdirectory. Hooks live in the main repo’s.git/hooks. If you setcore.hooksPath, make sure it’s an absolute path or all worktrees share the location.- Windows: long path errors. Nested node_modules in deep worktree paths exceed Windows MAX_PATH. Enable long paths:
git config --system core.longpaths trueand the matching Windows registry key. git worktree addis slow on large repos. It checks out every file. For frequent worktree creation, considergit clone --filter=blob:nonepartial clones, or a sparse-checkout config that limits the file set.
For related git workflow issues, see Git detached HEAD, Git submodule update failed, Git stash pop conflict, and Git fatal not a valid object name.
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 fatal: A branch named 'x' already exists
How to fix 'git fatal: A branch named already exists' when creating or renaming branches — including local conflicts, remote tracking branches, and worktree issues.
Fix: Git "cannot lock ref" – Unable to Create Lock File
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.