Skip to content

Fix: Git Worktree Not Working — Branch Already Checked Out, Prune, Submodules, and Locked Worktrees

FixDevs ·

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 deps

Or 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 prune removes “stale” worktrees. A worktree is “stale” if its directory no longer exists at the recorded path. If you moved the directory by hand (without git 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 --recursive in each worktree.
  • gitignore is global but per-worktree state isn’t. Files like .env need 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-x

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

If 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-x

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

repair 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 -v

If 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.ago

Combined with regular --dry-run checks, this is the safe pattern:

git worktree prune --dry-run
# Review the output.
git worktree prune

Common 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-drive

Lock 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 --recursive

If 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-x

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

Common 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 repository inside a worktree. The link file .git inside 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. Run git worktree repair from 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 true and configure per-worktree.
  • git pull in 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 status will show it. Have B run git pull (or git reset --hard) to catch up.
  • git fetch --all is 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 stash in a worktree applies in a different worktree. Stashes are global, indexed by stash ID. Use git stash list and git stash pop stash@{N} explicitly to apply the right one.
  • pre-commit / husky hooks don’t run. Each worktree has its own .git link, not a .git/hooks directory. Hooks live in the main repo’s .git/hooks. If you set core.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 true and the matching Windows registry key.
  • git worktree add is slow on large repos. It checks out every file. For frequent worktree creation, consider git clone --filter=blob:none partial 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.

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