Blog
Git worktrees explained
A worktree gives one repository several working directories that share a single object store, not several copies of your project. The part people miss is everything that single store still leaves shared between them.
A git worktree is a second working directory attached to the same repository: one .git object store underneath, a separate checkout and branch on top. git worktree add ../feature-x -b feature-x creates one, git worktree list shows every one on the repository, and git worktree remove or git worktree prune clean them up. The hard rule: a branch can be checked out in only one worktree at a time. What a worktree does not do is isolate your environment. Untracked and gitignored files like .env are never copied into a new one, and hooks, Git config and your credentials are shared across every one of them by default.
What a git worktree actually is
A repository normally has one working directory: the files you see when you cd into it, tied to whatever branch HEAD currently points at. git worktree add gives it a second one, a separate directory on disk with its own HEAD and its own branch, backed by exactly the same .git object database. Git calls the original checkout the main worktree and every one you add after it a linked worktree.
Nothing about the commit history is duplicated. Commits, blobs and trees live once, in one object store, and every worktree reads from it. What differs per worktree is just the pointer into that history: HEAD, the index, and which branch is checked out where. A repository's refs (refs/heads/*, refs/tags/*) are shared across every worktree too, with a small set of exceptions for things like an in-progress bisect.
The bookkeeping for each linked worktree lives in .git/worktrees/<name> inside the main repository, a small directory of administrative files Git uses to track where that worktree is and what it has checked out. You never touch it directly; git worktree list and git worktree remove do.
The commands: add, list, remove, prune
Four subcommands cover almost everything you need day to day.
git worktree add ../path -b new-branchcreates a worktree at../pathon a new branch. Drop-bto check out an existing branch instead, as long as nothing else already has it checked out.git worktree listshows every worktree attached to the repository, its path, and the branch sitting in it. Add--porcelainfor script-friendly output.git worktree remove <path>deletes a worktree, but only a clean one. Uncommitted changes or untracked files need--force, and the main worktree can never be removed this way.git worktree pruneclears the administrative entry for a worktree whose directory is already gone. This matters because deleting the folder by hand, withrm -rf, does not tell Git anything; the entry just goes stale.
Why a branch can only live in one worktree at a time
Try to check out a branch that is already checked out somewhere else and Git refuses outright, naming the worktree that has it. That is not a style preference, it is what keeps HEAD meaningful. Each worktree has its own index and its own idea of what is staged, but the branch ref itself is shared history, not a per-worktree copy, so two directories cannot both be its current tip.
--force can override the refusal, and the reason to avoid it is concrete rather than theoretical. Force two worktrees onto the same branch and commit in the first one: the branch ref moves, but the second worktree's index does not know it moved, so Git reports changes there that nobody made, a working tree quietly out of sync with the branch it is still sitting on.
Detached HEAD does not have this problem. Two worktrees can both sit on the exact same commit with git worktree add -d, because neither is tracking that commit as a moving branch tip the other could pull out from under it.
Worktree, a second clone, or git stash
A second git clone gives full isolation: its own .git directory, its own object store, its own remotes to configure, nothing shared with the original. That costs disk proportional to the whole repository's history, and doubles the bookkeeping, since fetching in one clone does nothing for the other. A worktree gets the same two-branches-checked-out-at-once result for a fraction of the cost, because the object store underneath is the one thing it does share.
git stash solves an adjacent problem and is not a substitute. It does not give you a second directory at all. It shelves whatever is uncommitted in the one directory you already have, puts the working tree back to a clean HEAD, and lets you switch branches in place. Stashes are also repository-wide rather than per-worktree, since they are stored as a ref: one made in any worktree shows up in git stash list from every other worktree on that repository.
Reach for a clone when you want independence from whatever happens to the other checkout next. Reach for a worktree when you want several branches checked out at once without paying for several histories. Reach for stash when you want uncommitted work out of the way for a minute, not a second branch open side by side.
What a worktree does not isolate
A worktree isolates files that Git tracks, on the branch it is checked out to. Everything outside that stays exactly where it already was, because a worktree is still the same machine, the same user and the same repository underneath.
- Untracked and gitignored files. A new worktree is a checkout of what Git tracks, so a
.envsitting in your main checkout, gitignored and never committed, does not appear in a new worktree at all. If something needs it, it has to be put there deliberately. - Hooks.
.git/hooks(or wherevercore.hooksPathpoints) resolves to the directory every worktree shares, not a per-worktree copy, so a pre-commit hook installed once runs in every worktree you create, with no way to scope it to just one. - Git config. The repository's
configfile is shared across every worktree by default, so a setting changed in one is live in all of them unlessextensions.worktreeConfighas been turned on for that particular setting. - Your credentials. SSH keys, a Git credential helper, a token sitting in your shell environment: none of that is part of the repository, so a worktree does nothing to it either way. Every worktree authenticates as you, because it is you.
- Ports and local databases. Run a dev server or point at a local database from two worktrees at once and the same collisions show up as from two terminal tabs in one directory, because a worktree changes which files a process reads, not which process, port or database it talks to.
Cleanup pitfalls
The mistake that catches people is deleting a worktree the wrong way. rm -rf the directory and Git has no idea: the entry stays in .git/worktrees, git worktree list keeps showing the path marked prunable, and it sits there until git worktree remove notices the directory is gone and cleans up, or git worktree prune is run directly.
git worktree remove itself refuses anything unclean. Uncommitted changes or untracked files mean --force is required, and a worktree locked with git worktree lock (the right move for one on a removable drive or a network share that might not stay mounted) needs --force passed twice: once to override the dirty check, once to override the lock.
And the main worktree, the original checkout a clone or git init created, cannot be removed through this command at all. Linked worktrees only exist in relation to it; the main one goes away when the repository does.
How this works in Forkbench
This is the exact primitive two other pages on this site build directly on top of: a parallel Claude Code agents workflow and the cross-vendor walkthrough for running several agents on a Mac both start from one worktree per agent, so two of them never write the same directory. Forkbench is a desktop app for running coding agents, and its version of that first step is a toggle in the git panel: putting a session on its own worktree is one click, not a git worktree add typed by hand.
The limit is the one this page has been pointing at throughout, and Forkbench states it directly rather than leaving it to be discovered: an agent in a terminal has the same filesystem access the human running it does, so it can read a .env sitting in the project, and the only thing that keeps that file off a working branch is actually being on a worktree in the first place.
Related: A parallel Claude Code agents workflow that holds up, How to run multiple Claude Code agents in parallel on a Mac, Stop parallel agents from undoing each other's work
Frequently asked
What is a git worktree?
A second working directory attached to a repository you already have, checked out to its own branch, backed by the same object store as every other worktree on that repository. Create one with
git worktree add ../path -b branch-name. Unlike a second clone, nothing about the commit history is duplicated: only the checkout, the branch pointer and the index are separate per worktree.Can I check out the same branch in two worktrees?
Not by default. Git refuses with an error naming the worktree that already has it, because the branch ref is shared history and two directories both claiming its tip would desync the moment either one commits.
--forceoverrides the refusal, but it is worth avoiding: commit in one and the other's index goes stale against a branch pointer it no longer matches, which shows up as changes nobody made.Does a git worktree copy my .env file?
No. A worktree is a fresh checkout of whatever Git tracks, and
.envis normally untracked and gitignored, so a new worktree simply does not have it. If a build or an agent needs that file present, something has to put it there on purpose;git worktreehas no opinion about files it was never tracking in the first place.What is the difference between a worktree and git stash?
They solve different problems. A worktree gives you a second directory so two branches can be checked out and edited at the same time.
git stashkeeps you in the one directory you have, shelving uncommitted changes so you can switch branches cleanly and bring them back later. Stashes are also repository-wide rather than per-worktree: one made in any worktree shows up ingit stash listfrom all of them.How do I delete a git worktree without breaking anything?
Run
git worktree remove <path>from inside the repository, notrm -rfon the folder. Deleting the directory by hand leaves Git's bookkeeping behind, so the worktree keeps appearing ingit worktree listmarked prunable until it is cleaned up withgit worktree removeorgit worktree prune. An unclean worktree needs--force; a locked one needs--forcetwice.