7 min readcode

Git Worktrees: Parallel Work Without Clobbering Your Workspace

A practical workflow for separate tasks, builds, and AI coding agents in one repository

Git Worktrees: Parallel Work Without Clobbering Your Workspace

You are halfway through a search refactor when an urgent bug arrives. Or an AI coding agent is editing the API while you want another agent to work on tests. One checkout forces you to stop, stash, switch branches, and later reconstruct the context you had. Two agents in that checkout can overwrite each other's files or mistake unfinished work for their own.

Git worktrees give each task a separate working directory and branch while keeping them attached to the same repository. This is useful for human parallelism, and especially useful now that several AI agents can produce useful code at the same time. Parallel coding only helps when each task has an explicit boundary and the results can be integrated safely. Worktrees provide the filesystem boundary; they do not make design decisions or resolve conflicting changes for you.

Here is a workflow you can try with a repository you already have.

What a worktree actually separates

A linked worktree has its own checked-out files, HEAD, index, and uncommitted changes. The worktrees share the repository's objects and branch references. You can have main open in one directory, feature/search-index in another, and feature/api-tests in a third without cloning the repository three times. Git documents this model in git-worktree.

workspace/
├── project/                         main; clean integration checkout
└── .worktrees/
    └── project/
        ├── search-index/            feature/search-index
        └── api-tests/               feature/api-tests

This layout keeps task checkouts outside the main checkout, so a file search or build in project/ does not accidentally traverse the other tasks. Choose a location your tools can discover and that your team can inspect. git worktree list shows every worktree Git knows about, regardless of where you put it.

Git normally prevents the same branch from being checked out in two worktrees at once. That safeguard matters: each task needs its own branch. Starting two tasks from the same commit is fine; letting them edit the same branch or directory is the collision we are trying to avoid.

Create two task checkouts

Run these commands from the existing project/ checkout while main is clean:

mkdir -p ../.worktrees/project
git worktree add -b feature/search-index ../.worktrees/project/search-index main
git worktree add -b feature/api-tests ../.worktrees/project/api-tests main
git worktree list

The -b flag creates each branch at main and checks it out in the new directory. The final main is the starting commit, stated explicitly so the result does not depend on whichever branch happens to be active in your shell. The Git manual describes both add and list.

Give each task a short, testable objective. For example: “Add an index for the order search query and measure the plan before and after” in search-index; “Add API contract tests for the current order response” in api-tests. If both tasks need to rewrite the same handler, separate worktrees will prevent filesystem collisions but the integration may still be difficult. Split the work by stable interfaces, or sequence the dependent edits.

Move into one directory to work on it:

cd ../.worktrees/project/search-index
git status --short --branch
# Edit, run checks, and commit on feature/search-index.

From that directory, git status describes only that task's checkout. Changes there will not appear in the api-tests working directory. New commits and branches do become visible through the shared repository, which is why integration still needs coordination.

Give each checkout its own runtime

Worktrees separate tracked files and untracked files on disk. They do not automatically isolate everything your application uses. A local database, a Docker project name, an output bucket, and TCP port 3000 can still be shared accidentally.

For a Node project, install dependencies in each worktree. Do not symlink one node_modules directory into several worktrees: package tooling and build caches can resolve paths relative to the checkout. Use the project's lockfile and version manager, then start each server on a distinct port. Start each terminal in project/ for this example:

cd ../.worktrees/project/search-index
npm ci
npm run dev -- --port 3101

# In a second terminal:
cd ../.worktrees/project/api-tests
npm ci
npm run dev -- --port 3102

The commands are illustrative; use your repository's build and start commands. Keep task-specific environment variables and local data separate where those resources can interfere. Never copy production secrets into a worktree merely to make a local preview start.

Ignored files are also local to a worktree. A .env.local file or generated build in one directory does not appear in another. This is convenient, but it means a fresh worktree may need its own safe local configuration before it can run.

One agent, one worktree, one objective

An AI agent should be given the absolute path of its worktree and a bounded task. That makes its file operations, terminal commands, and build artifacts easy to attribute. A useful brief looks like this:

Work only in /workspace/.worktrees/project/search-index.
Goal: improve the order search query without changing the API response.
Run the repository's relevant tests and report the query plan change.
Commit your work on feature/search-index; do not integrate or deploy it.

Give the second agent a different directory and objective. Do not send two agents into the same worktree, even if their prompts mention different files. A formatter, generated schema, package update, or broad search-and-replace can cross that informal boundary.

Before starting another agent, ask whether the tasks are truly independent. One agent defining a new API while another writes tests against the old API is likely to create rework. A better split may be “agree on the contract first, then implement producer and consumer in separate branches.” Parallel execution is a scheduling tool, not a substitute for shared design.

It also helps to keep one owner for integration. Agents can propose commits and report their checks, but the integrator should review the combined behavior, resolve conflicts, and run the full verification after combining changes. A passing build in each branch does not prove the combination passes.

Integrate one result at a time

Assume feature/search-index is ready. Starting in project/, check its diff and verification results in its worktree, then update it onto the latest main:

cd ../.worktrees/project/search-index
git status --short --branch
git diff main...HEAD
git rebase main
# Run the project's required checks again after the rebase.

git diff main...HEAD shows committed changes on the task branch relative to the common ancestor. Review uncommitted changes separately with git diff and git diff --cached; do not assume the three-dot diff includes them. Rebase only a branch whose history your workflow permits you to rewrite. If it is shared with other people, agree on an update strategy before rewriting it.

When the branch passes, integrate according to your repository's policy. For a team using squash commits, the main checkout can do this:

cd ../../../project
git merge --squash feature/search-index
git commit -m "perf: speed up order search"

The relative cd above assumes the exact layout shown earlier and starts in search-index/; adjust it if you chose another directory. Squash creates one integration commit on main. A normal merge or pull request can be equally appropriate if that is your team's policy. The point is to integrate one reviewed result, then update the next branch against the new main.

Now feature/api-tests still starts from the old main. Rebase it, resolve any overlap, and rerun its checks before integrating it. Finally run the combined repository checks on main. Conflicts are useful information: they show that the tasks were coupled at a code boundary you may want to improve next time.

Clean up without losing work

Once a task has been integrated and you have checked that nothing remains uncommitted, retire its checkout:

git worktree list
git worktree remove ../.worktrees/project/search-index

Run those commands from the main project/ checkout. git worktree remove refuses to remove a dirty worktree by default. Do not use --force as a routine cleanup step. If you used a squash merge, Git may not consider the original branch “merged” because its commit is not literally in main; after verifying the squash contains the intended changes, delete that local branch with git branch -D feature/search-index. With a normal merge, git branch -d can check ancestry for you.

Avoid deleting a worktree directory by hand. Git retains administrative records for linked worktrees; use git worktree remove. If someone has already deleted a directory, inspect git worktree list and use git worktree prune only for stale entries. The worktree cleanup documentation explains these commands.

A small operating checklist

The practical habit is simple:

  1. Start each task from a known commit on its own branch and worktree.
  2. Assign one person or agent to that worktree and keep the task narrow.
  3. Isolate dependencies, ports, caches, and local data that could collide.
  4. Review the diff and checks in each branch; integrate sequentially.
  5. Verify the combined result and remove finished worktrees.

Worktrees make it cheap to keep several contexts open at once. AI makes producing changes in those contexts faster. The engineering value comes from keeping each change understandable, testable, and safe to combine.

Further reading