You’re three edits deep into a feature, nothing compiles, and someone drops “can you hotfix prod real quick.” The reflex is muscle memory: git stash, git switch main, fix it, switch back, git stash pop, and then spend ten minutes remembering what half-finished thought you stashed.
git worktree deletes that whole dance. It checks out a second branch into a second directory that shares the same repo. Your half-done work sits exactly where you left it — a different folder, a clean tree, same .git. No stash. No context to rebuild.
Every command below was run for real. Both ways it bites you stay in, because both are going to happen to you.
Set up a repo you’re mid-change on
One commit, then a deliberately dirty tree — you’re partway through something:
$ git init -q -b main project && cd project
$ echo "print('hi')" > app.py
$ git add app.py && git commit -q -m "initial commit"
$ echo "print('half-done work')" >> app.py # you, mid-thought
$ git status --short
M app.py
That ` M app.py` is your uncommitted work. The old way, you’d have to hide it before you could touch another branch. Watch what worktree does instead.
Add a second working tree, no stash
git worktree add <path> -b <newbranch> <start-point> creates a new directory with a fresh branch checked out from where you point it:
$ git worktree add ../hotfix -b hotfix main
Preparing worktree (new branch 'hotfix')
HEAD is now at c211bbb initial commit
Now list what you’ve got checked out:
$ git worktree list
/tmp/demo/project c211bbb [main]
/tmp/demo/hotfix c211bbb [hotfix]
Two branches, checked out at the same time, in two folders. cd ../hotfix, fix prod, commit, push. Your feature work never moved:
$ cat app.py # in project/ — still dirty, untouched
print('hi')
print('half-done work')
$ cat ../hotfix/app.py # in hotfix/ — clean checkout of main
print('hi')
You’ll know it worked when git worktree list shows two paths, the hotfix tree is clean, and your half-done line is still sitting in the original folder. No stash entry to remember.
One detail that surprises people: the .git in a worktree is a file, not a directory. It’s a pointer back to the real repo:
$ cat ../hotfix/.git
gitdir: /tmp/demo/project/.git/worktrees/hotfix
That’s why the history, remotes, and config are shared — there’s exactly one .git database, and every worktree links to it.
The part where it breaks: one branch, one tree
The first thing everyone tries is checking out main a second time so they have a “clean copy.” Git refuses:
$ git worktree add ../another main
Preparing worktree (checking out 'main')
fatal: 'main' is already used by worktree at '/tmp/demo/project'
This is a feature, not a bug. If two directories had main checked out, a commit in one would leave the other’s index and working tree lying about what HEAD is — a great way to “lose” a commit. So git enforces one branch, one working tree. If you genuinely want a second copy of the same branch’s contents, check out a new branch that starts there (-b review main) or use a detached checkout (git worktree add --detach ../peek main), which isn’t “on” any branch and so can’t collide.
The other part where it breaks: the stale ghost
Here’s the one that bites weeks later. You finish the hotfix, and you clean up the obvious way — you delete the folder:
$ rm -rf ../hotfix
Feels done. It isn’t. Git still thinks that worktree exists, because you removed the directory but not git’s bookkeeping entry for it:
$ git worktree list
/tmp/demo/project c211bbb [main]
/tmp/demo/hotfix c211bbb [hotfix] prunable
See that prunable tag — git is telling you it’s a ghost: the branch is still locked to a worktree whose files are gone, so trying to check hotfix out elsewhere still fails. Clear the bookkeeping with prune:
$ git worktree prune -v
Removing worktrees/hotfix: gitdir file points to non-existent location
$ git worktree list
/tmp/demo/project c211bbb [main]
Now it’s actually gone. The lesson: don’t rm -rf a worktree — use git worktree remove <path>, which deletes the directory and the bookkeeping in one step (and refuses if you have uncommitted changes there, which rm -rf would have silently eaten).
The whole safe flow, tested
Here’s the shape to reach for, wired to add a worktree, prove the original tree stays dirty and untouched, and tear it down the clean way. This block is opted into our test harness (lh:run) and runs on every build in a locked-down, no-network sandbox, so the version you’re reading is the version that passed:
```bash lh:run #!/usr/bin/env bash set -euo pipefail
export GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com export GIT_COMMITTER_NAME=you GIT_COMMITTER_EMAIL=you@example.com
root=”$(mktemp -d)”; cd “$root” git init -q -b main project && cd project echo “print(‘hi’)” > app.py git add app.py && git commit -q -m “initial commit”
You’re mid-change: leave the tree dirty on purpose.
echo “print(‘half-done work’)” » app.py
echo “==> main tree is dirty:” git status –short
Second working tree for a hotfix branch — no stash, no commit.
git worktree add -q ../hotfix -b hotfix main
echo “==> two worktrees checked out at once:” git worktree list
echo “==> hotfix tree is clean; your half-done work is untouched:” git -C ../hotfix status –short && echo “ (hotfix: nothing to show — clean)” grep -q “half-done” app.py && echo “ (main: your uncommitted line is still here)”
Clean up the RIGHT way — removes the dir AND the bookkeeping.
git worktree remove ../hotfix echo “==> after ‘worktree remove’, back to one:” git worktree list echo “done” ```
All the console output above is real, captured with git version 2.54.0.
When this goes wrong
fatal: '<branch>' is already used by worktree at …— you tried to check out a branch that’s live in another tree. That’s the one-branch-one-tree rule. Start a new branch from it (-b) or use--detach.- A
prunableentry that won’t die — yourm -rf‘d a worktree instead ofgit worktree remove. Rungit worktree pruneto clear the ghost, then useremovenext time. worktree removerefuses — it won’t delete a tree with uncommitted changes or untracked files, on purpose. Commit them, or force it with--forceonce you’re sure there’s nothing to lose.- Submodules and worktrees are still awkward. If your repo has submodules, each worktree needs its own
git submodule update; they aren’t shared like the main history is. Budget a minute for it.
The reflex was stash-switch-fix-switch-pop. The replacement is one git worktree add and a cd. Your unfinished thought stays on disk, in its own folder, exactly where you left it — and the only thing you have to remember is to tear it down with remove, not rm.