fatal: 'feature' is already checked out at '/home/rezha/projects/my-app'That error shows up when you are halfway through something and need to look at another branch. The usual advice is to stash, switch, do the other thing, switch back, and pop the stash. It works. It also asks you to remember where you were, what was stashed, and whether the pop will apply cleanly. A worktree skips that whole dance. You open a second folder for the second branch and leave the first folder alone.
The stash dance, and the shorter version
The stash version looks like this:
git stash push -m "wip: half done refactor"git switch main# fix the bug, commit, pushgit switch featuregit stash popThe worktree version looks like this:
git worktree add ../my-app-hotfix -b hotfix maincd ../my-app-hotfix# fix the bug, commit, pushcd ../my-appYour feature branch never moved. Nothing was stashed. Nothing was popped. The hotfix folder stays on disk until you remove it, and removing it does not delete the branch.
This is not a new feature. Worktrees shipped in git 2.5, in 2015. The reason they still feel unfamiliar is that the normal model is one folder, one checkout. A worktree breaks that assumption in one specific way: a single repository can have several working folders at once, each on its own branch.
One repo, many checkouts
Git calls the folder you get from git init or git clone the main worktree. Every folder you add with git worktree add is a linked worktree. The git-worktree docs put it plainly: a repository has one main worktree, if it is not bare, and zero or more linked worktrees.
The split in that diagram is the whole idea. History, branches, and configuration live once, in the shared repository. The checked out files, the current branch pointer for that folder, and the staging area live once per folder. A commit made in any worktree lands in the same object store, so every other worktree can see it right away with git log --all. No push or pull between folders is needed, because there is nothing to sync. There was never a second copy.
git worktree list shows the current state:
$ git worktree list/tmp/my-app c2 [main]/tmp/my-app-hotfix c2 [hotfix]The main worktree is listed first, then the linked ones, each with the commit it sits on and the branch it has checked out. Add -v for locked and prunable annotations. Add --porcelain when a script needs to parse the output. That format stays stable across git versions, and -z handles paths with newlines in them.
What add puts on disk
Run git worktree add ../my-app-hotfix -b hotfix and git does three things. It creates the folder, checks out the branch into it, and writes a small admin folder for it inside the main repository. That admin folder is where the per worktree state lives.
my-app/main worktree · branch main
- .git/the real repository
- objects/shared
- refs/shared
- configshared
- hooks/shared
- worktrees/hotfix/admin folder
- HEADper worktree
- indexper worktree
- commondir→ ../..
- gitdir→ my-app-hotfix/.git
- a.txt, ...working files
my-app-hotfix/linked · branch hotfix
- .gita file, not a foldergitdir: …/my-app/.git/worktrees/hotfix
- a.txt, ...own working files
Two pointers make the link. The .git file points at the admin folder, and gitdir points back.
Open the linked folder and .git is a file, not a folder. It holds one line:
$ cat ../my-app-hotfix/.gitgitdir: /path/my-app/.git/worktrees/hotfixThat is a gitfile, the same trick submodules use. It points git at the admin folder. Inside the admin folder, two link files point back. commondir holds ../.., a relative path back to the main .git. gitdir holds the absolute path back to the linked folder. Git uses that back link to notice when a worktree folder has been deleted.
You can see the split with two commands, run inside the linked folder:
$ git rev-parse --git-dir --git-common-dir/path/my-app/.git/worktrees/hotfix/path/my-app/.git--git-dir is the private admin folder. --git-common-dir is the shared repository. Git picks one or the other depending on what it is looking up. HEAD and index come from the private folder. Branch refs come from the shared one. The docs warn against guessing which is which from the path alone, and point at git rev-parse --git-path instead.
The admin folder holds more than HEAD and index. On the scratch repo I ran for this post, git 2.43.0, it held HEAD, ORIG_HEAD, commondir, gitdir, index, and logs. The rules for what is shared are in the docs, in the REFS section: all pseudo refs are per worktree, and all refs under refs/ are shared, with two exceptions, refs/bisect and refs/worktree. HEAD is a pseudo ref, so each worktree gets its own. A branch is refs/heads/<name>, so every worktree sees the same pointer, and that is why the next section matters.
Configuration follows the same split, with one twist. By default the repository config file is shared across all worktrees. A hook you install in .git/hooks runs from every worktree. I checked both: a pre-commit hook in the main repo ran when I committed from the linked folder, and a user.email set in the main repo showed up in the linked one. Stash is shared too. A stash created in one worktree shows up in git stash list in the others, with the branch name it was created on. Stash in a worktree workflow the way you would in a single folder workflow and you will pop the wrong work in the wrong place. Commit instead.
There is an escape hatch for config. Turn on extensions.worktreeConfig and git reads a config.worktree file after the shared config. You change it with git config --worktree, run inside the worktree you want to change. I ran it inside a linked worktree and only that worktree picked up the new email. The main worktree kept the shared value. The docs list the settings that should not be shared this way, and core.sparseCheckout is on that list unless you are sure every worktree uses sparse checkout.
Walk through it
The simulator below runs the sequence from the previous sections. Step through it and watch three things: the folders, the HEAD in each folder, and the shared store underneath. The branch pointer for hotfix moves once, in step 3, and every folder can see the new commit after that.
Folders on disk
Shared store, my-app/.git
Sample run. The commands are real and were run on git 2.43.0. Hashes are shortened to c2 and c3 so the movement is easy to follow. No git runs in your browser here.
Two details in that run are easy to miss. First, the commit in step 3 is visible from the main folder without any fetch. It was never in a second repository. Second, the refusal in step 4 changes nothing. Git does not create the folder, and the branch pointer does not move.
Why one branch gets one checkout
A branch is a pointer to a commit. HEAD in a worktree points at a branch, and the branch points at a commit. Two worktrees on the same branch would mean two folders, two indexes, and two sets of working files all claiming to be the same pointer. A commit in one folder would move the pointer while the other folder still had the old files on disk. Git refuses the second checkout instead of sorting that out later.
The fix is almost always a new branch name from the same base commit:
git worktree add ../my-app-review -b review hotfixNow review and hotfix are separate pointers that happen to start at the same commit. They can move independently from here.
Two other forms are worth knowing. --detach checks out a commit without a branch at all, which is the right shape for a throwaway look at an old commit or a test run:
git worktree add --detach ../my-app-old HEAD~3And -f overrides the safeguard and lets two folders share one branch. I ran it in the scratch repo and it worked. I would not use it. Two folders sharing a pointer is the exact confusion the refusal exists to prevent, and the only thing -f buys is a faster way into it.
The commands, with their failure modes
Add and list
add has a naming shortcut that saves a flag most of the time. With no branch argument, git names the branch after the last part of the path. git worktree add ../hotfix creates branch hotfix if it does not exist, and checks out the existing hotfix branch if it does and nothing else has it checked out. With an explicit branch, git worktree add ../review hotfix checks out hotfix in the new folder. With -b, you pick the name and the starting point separately: git worktree add -b hotfix ../my-app-hotfix main starts hotfix at main.
--no-checkout creates the folder and the admin entry without checking files out. The folder holds only a .git file until you check something out yourself. The docs point at sparse checkout setup as the reason you would want that.
Remove, prune, and lock
remove deletes the folder and its admin entry. It refuses a dirty folder:
$ git worktree remove ../my-app-hotfixfatal: '../my-app-hotfix' contains modified or untracked files, use --force to delete it--force deletes it anyway, including uncommitted work. The branch stays in both cases. I have deleted a worktree folder with rm -rf more than once, and the fix is prune. Git notices the admin entry points at a missing folder, marks it prunable in list -v, and prune removes the entry. The branch and the commits stay. prune -n reports what it would remove without removing anything.
lock is for a worktree that looks deleted but is not. A folder on a USB disk or a network share reads as missing whenever the disk is unmounted, and prune would clean up its admin entry. Locking writes a locked file, with your reason in plain text, into the admin folder, and prune leaves it alone. unlock reverses it. A locked worktree also cannot be moved or removed without extra force.
Move and repair
move renames a worktree folder and updates the pointers in both directions. The admin folder keeps its old name. I moved a folder called wt-move to wt-moved and the admin folder stayed worktrees/wt-move, with the gitdir file updated to the new path. If you move a folder by hand with mv, run repair with the new path from the main worktree and git rewrites the pointers:
git worktree repair ../wt-movedThe same command fixes the other direction. Move the main worktree by hand and the linked folders cannot find it until repair runs in the main folder.
A bare repository has no main worktree, and that is a useful shape for this. Clone or init with --bare, then add worktrees from it. Every checkout is a linked worktree, and there is no main folder to confuse with the rest:
git init --bare my-app.gitgit -C my-app.git worktree add ../my-app-main maingit -C my-app.git worktree add ../my-app-hotfix hotfixWorktree, clone, stash, or switching
| Approach | Disk cost | History | Context kept |
|---|---|---|---|
| Worktree | Working files only, objects shared | Same store, commits visible at once | Yes, each folder stays where it was |
| Second clone | Full copy, objects duplicated | Separate store, push and pull to sync | Yes, but the copies drift apart |
| Stash and switch | Nothing extra | Same store | No, the stash has to be popped back |
| Switch in place | Nothing extra | Same store | No, one branch at a time |
Stash is still the right call when the interruption is tiny and you will be back in a minute. A second clone is almost never the right call for this problem. It duplicates the whole history and then needs syncing to stay current. Reach for a worktree when two branches need to be alive at the same time: running, building, or being edited, not just looked at.
The parts that still bite
Submodules are the sharpest edge. The git-worktree docs end with a BUGS section that says support for submodules is incomplete and that multiple checkouts of a superproject are not recommended. The move command refuses worktrees that contain submodules. If your repo has submodules, test the worktree flow on a scratch copy before you rely on it.
Build output is the quiet cost. Objects are shared, working files are not. Two worktrees mean two node_modules folders, two build caches, and two sets of ignored files. An ignored file created in one folder does not appear in the other. I checked that too: a build/ folder in one worktree stayed invisible to the rest, exactly as .gitignore says it should, and the other folder had no build/ at all.
Paths are the other one. The admin folder stores the absolute path back to the linked folder, so moving the whole repository to another mount point or another machine breaks the link. repair fixes it, and newer git versions have work on relative paths for this case, but on the git I tested, 2.43.0, the stored path was absolute. If you move repos around, run list first and look for prunable before you assume a worktree is gone.
Parallel agents, briefly
This is where worktrees stopped being a hotfix trick and became infrastructure. One agent per worktree, one branch per agent, one shared object store underneath. Each agent edits its own folder, commits on its own branch, and the branches show up in the same ref list. A hook installed once runs for all of them, because hooks are shared. The same rules apply: one branch per folder, no shared stash, and a cleanup step at the end. An agent that deletes its own folder without remove leaves the same prunable admin entry a person would leave, and prune cleans it up the same way. The parallel agents write-up covers the port and database sharing problems that git does not solve for you. Worktrees give each agent its own files. They do not give each agent its own database, its own ports, or its own cache.
Clean up
Keep worktrees short lived. Name the folder after the branch, or after the job if the branch name would collide with a folder you already have. Run list before you start work you care about, so a stale prunable entry does not surprise you when you try to check a branch out.
When the work is done, commit, push, and remove the folder:
git worktree remove ../my-app-hotfix