Git Worktrees: Separate Editing Spaces, Shared History
If two agents work on the same project, you want their edits to stay out of each other’s way—but you also want them to exchange finished work easily. Git worktrees provide exactly that balance: separate directories for editing, backed by one shared repository.
The trick is understanding where Git draws the boundary:
Each worktree has its own working files, staging area, and
HEAD. The worktrees share the repository’s stored history and ordinary branch references.
Let’s unpack what that means, from the files you edit to the pointers Git maintains behind the scenes.
One Repository, Several Places to Work
A worktree is a directory containing a checked-out version of your project: the files you can open, edit, build, and test. Git lets you attach additional worktrees to an existing repository, so you can work on several branches at once without switching one directory back and forth.
Imagine this arrangement:
project/ on main
project-auth/ on feature/auth
project-search/ on feature/search
You could work in project/, assign an authentication agent to project-auth/, and assign a search agent to project-search/.
Each directory contains its own project files. If the authentication agent changes src/login.ts, it changes the copy inside project-auth/—not the copy in either of the other directories.
But these are not three independent repositories. They are three working spaces attached to the same underlying repository.
A useful analogy is a shared library with several desks. Each desk has its own papers and work in progress, while everyone draws on the same archive. Finishing and filing something makes it available to everyone; it does not rearrange the papers on everyone else’s desk.
The Boundary: What Is Private and What Is Shared?
Git separates the current state of your work from the recorded history of the project.
Here are the important pieces:
| Part | What it means | Separate for each worktree? |
|---|---|---|
| Working files | The actual files you open and edit | Yes |
| Index, or staging area | The proposed snapshot for your next commit | Yes |
HEAD | The reference identifying your current branch or checked-out commit | Yes |
| Repository objects | Stored commits, file contents, and directory snapshots | No—shared |
| Ordinary branch references | Names such as main and feature/auth that point to commits | No—shared |
The first three describe what you are working on right now. The last two provide the history and names through which worktrees share recorded work.
Working files: your local editing space
Working files are the files you see in an editor or terminal. Each worktree has its own copies.
For example, an edit to:
project-auth/src/login.ts
does not edit:
project-search/src/login.ts
Even if both files originally came from the same commit, they are separate files in separate directories.
This is the most visible kind of isolation. One agent can rewrite authentication code while another edits search code, without their uncommitted changes appearing in the same working directory.
The index: your private next-commit plan
The index, often called the staging area, holds the snapshot Git will use for your next commit.
When you run:
git add src/login.ts
Git stages that file’s current contents. You can keep editing afterward, so the staged version and the working file do not necessarily remain identical.
Every worktree has its own index. That means the authentication agent’s git add does not stage anything for the search agent.
This distinction matters because isolation is more than having separate folders. If the folders shared a staging area, one agent could accidentally include another agent’s changes in its commit. Separate indexes prevent that particular collision.
HEAD: your private “you are here” marker
HEAD tells Git what you currently have checked out.
Usually, it points to a branch name:
project-auth: HEAD → feature/auth
project-search: HEAD → feature/search
You can also check out a specific commit without being on a branch. That is called a detached HEAD: your “you are here” marker points directly to a commit rather than to a branch.
Each worktree has its own HEAD, so different worktrees can occupy different positions in the repository’s history at the same time.
What Happens When an Agent Edits, Stages, and Commits?
The clearest way to understand the boundary is to follow one change through its lifecycle.
Suppose the authentication agent is working in project-auth/ on feature/auth, while the search agent is working in project-search/ on feature/search.
Step 1: Editing changes only one worktree
The authentication agent edits src/login.ts.
At this point:
- The working file in
project-auth/changes. - The corresponding file in
project-search/stays unchanged. - No new commit exists.
- No branch reference moves.
The search agent’s git status does not suddenly report the authentication agent’s edit. It reports the state of the search worktree.
Step 2: Staging changes only one index
Inside project-auth/, the authentication agent runs:
git add src/login.ts
Now the authentication worktree’s index contains the staged change.
The search worktree’s index is untouched. Its next commit will still be built from its own staging area, not from the authentication agent’s.
Step 3: Committing adds shared history
The authentication agent runs:
git commit -m "Improve login validation"
A commit is a recorded project snapshot, along with information such as its message and its connection to earlier history. Git stores the commit and the objects needed to represent that snapshot in the shared repository.
Because the agent is on feature/auth, Git also moves that branch’s reference to the new commit.
Two things are now shared:
- The newly recorded commit.
- The updated meaning of the name
feature/auth.
From the search worktree, you can inspect that history:
git log feature/auth
You do not need to copy the commit into a separate local repository first. Both worktrees already use the same repository storage.
Step 4: The other worktree’s files stay put
The new authentication commit does not automatically update the search worktree.
The search worktree still has:
- Its own working files.
- Its own staged snapshot.
- Its own
HEAD, pointing tofeature/search.
This is the central distinction:
A commit becoming available is not the same as that commit being checked out.
To bring the authentication work into the search branch, you would take an explicit action, such as a merge—combining branch histories—or a cherry-pick—applying the changes from a selected commit.
Shared history makes finished work accessible. It does not silently inject that work into another agent’s editing space.
The Subtle Part: Private HEAD, Shared Branches
It can sound contradictory to say that HEAD is private while branches are shared. The key is that these are different kinds of pointers.
A branch reference is a named pointer to a commit. For example, feature/auth identifies the current tip, or latest commit, of that branch.
A worktree’s HEAD usually points to that branch name.
Conceptually, you have:
Authentication worktree's HEAD
→ feature/auth
→ latest authentication commit
Search worktree's HEAD
→ feature/search
→ latest search commit
The two HEAD pointers belong to different worktrees. The branch names belong to one shared set of references.
So feature/auth is not a private branch that exists only inside project-auth/. You can refer to it from any of the worktrees. What is private is the authentication worktree’s choice to have that branch checked out.
Why Git normally blocks the same branch in two worktrees
Git normally prevents you from checking out the same branch in two worktrees simultaneously.
Why? Because a branch reference is shared. If two worktrees both treated the same branch as their active branch, a commit in one could move that shared pointer while the other still had working files and an index based on an earlier state.
That would create confusing interactions between a shared branch tip and two different editing states.
The normal pattern is therefore:
- One worktree on
main. - One worktree on
feature/auth. - One worktree on
feature/search.
Each worktree gets an independent editing context, while each active branch has a clear place where its work is happening.
What Lives Inside .git?
In a typical main worktree, .git is a directory containing repository data.
In an additional, linked worktree, .git is usually a small file instead. It tells Git where to find that worktree’s administrative metadata.
Conceptually, the layout separates two categories:
Private worktree metadata
The linked worktree’s own metadata includes:
- Its
HEAD. - Its index.
- Other information Git needs to manage that particular worktree.
This metadata lives inside the main repository’s administrative area rather than in a second full repository directory beside the linked worktree’s files.
Common repository data
The shared repository holds:
- The object storage used for recorded history.
- Ordinary branch references.
- Other common repository information.
Strictly speaking, Git stores snapshots through several kinds of objects: commits describe recorded revisions, while other objects store file contents and directory structure. You do not need to memorize those object types to use worktrees. The important point is that the worktrees share this storage rather than maintaining independent histories.
Git’s worktree documentation describes this split between per-worktree data and common repository data.
The Practical Mental Model
When you use worktrees for parallel agents, think in two layers:
- Private work in progress: each agent has its own files, staging area, and current checkout.
- Shared recorded work: commits and branch names are available across the worktrees.
That gives you both isolation and a straightforward way to exchange completed changes.
Just do not mistake worktrees for fully independent repositories. A change to shared branch references affects the shared repository, even though ordinary edits and staging remain local to one worktree.
The simplest rule to remember is:
Worktrees isolate what you are editing and preparing to commit. They share what Git has recorded—and the branch names used to find it.