← Posts

Git worktrees in the age of agents

Before agentic coding, I had never used Git worktrees. I knew they existed, filed under niche tricks, handy for hotfixing main without abandoning a half-written feature. But most of the time, a git stash did the job.

I rediscovered the feature when I started wondering how to properly isolate several agents working in parallel on the same codebase, locally.

The problem isn’t the agent, it’s the folder

An agent does the same work as a developer. It reads files, edits them, runs commands, sometimes commits. But it has a power I’ll never have: it can be in several places at once. In other words, it can spawn sub-agents or work alongside other, more or less similar agents, on the same codebase and at the same time.

Git’s model is decentralised on the server side. Everyone works on their local copy and syncs with a remote from time to time.

The idea behind worktrees is to decentralise local work too. Each branch comes with its own directory. No more stashing and checking out all the time.

Before, if I wanted to work on something completely different, I simply made a second clone. Not very elegant, but it did the trick.

What a worktree gives me

A worktree is a second (or nth) working directory attached to the same repository. Same history, same Git objects, but an independent checkout.

In practice:

git worktree add ../my-repo-feat-auth -b feat/auth

I get a sibling folder, on its own branch, ready for commits. The main checkout stays where I left it.

No re-clone. No re-syncing remotes.

So it’s just a separate clone?

No, and that’s the whole point.

In the new folder, .git isn’t a directory but a one-line file:

$ cat ../my-repo-feat-auth/.git
gitdir: /path/to/my-repo/.git/worktrees/my-repo-feat-auth

It points to a small directory created inside the main repository, holding only what belongs to that worktree:

$ ls .git/worktrees/my-repo-feat-auth
HEAD  ORIG_HEAD  commondir  gitdir  index  logs  refs

This means each worktree has its own HEAD (the checked-out branch), its own index (the staging area) and its own logs. The commondir file points back to the main repository for everything else.

What’s shared:

  • objects (commits, trees, blobs), so the whole history
  • branches and tags
  • config and hooks
  • remotes, so a fetch in one worktree benefits all of them
  • the stash, which is surprising the first time

What’s duplicated is only the working tree: the files tracked by Git, extracted from the objects at checkout time. A worktree weighs as much as a checkout, not as much as the repository. On a repo with ten years of history, the difference is huge.

And what isn’t copied at all: anything Git doesn’t track. .env, node_modules, .venv, build caches and other ignored files stay in the original folder. The new worktree starts without them. I’ll come back to that in the pitfalls.

This model also explains the one real limitation (a fairly natural one, in the end): a branch can only be checked out in one worktree at a time.

$ git worktree add ../other feat/auth
fatal: 'feat/auth' is already used by worktree at '../my-repo-feat-auth'

A branch is just a pointer to a commit, and that pointer is shared by every worktree. If I commit on feat/auth in one folder, the branch moves forward for the other one too, while its files haven’t changed. Its git status would show changes that undo the commit I just made.

Contrary to what I thought, there’s nothing recent about git worktree: it has been around since Git 2.5, in 2015.

So I spent ten years right next to it without ever feeling the need, and now I can’t go a day without it 😅.

My rule: one feature, one worktree

Since I started relying on agents to produce code, I’ve had a simple rule: every feature goes through a worktree.

Concretely, the cycle looks like this:

  1. I create a dedicated branch and worktree
  2. I open the agent session in that folder
  3. the agent (or me, or both) iterates there
  4. I push, open the PR
  5. once merged, I remove the worktree

The main repo stays my anchor, usually on main or develop.

The benefit is that interruptions barely cost anything anymore. If an urgent topic jumps to the top of the stack, I immediately open a worktree from a stable state, without touching my work in progress.

What changes day to day

The obvious win, and what makes worktrees relevant today, is being able to work on several topics in parallel.

Before, "working in parallel" often meant stacking contexts in my head and in the stash. With one worktree per feature, parallel means: two folders, two branches, two goals. Agent A doesn’t see the half-written files of feature B.

I can also review code while a feature moves forward elsewhere. No need to choose between the two anymore.

The pitfalls (because there are some)

It isn’t magic.

Conflicts. Working in parallel on topics that overlap doesn’t make conflicts go away. They just show up later, at merge time, and still need to be resolved.

Ignored files. As mentioned above, nothing Git doesn’t track gets copied. Dependencies (node_modules, .venv, build caches) have to be reinstalled, and on a large monorepo you feel it. It can also cause side effects, like tests failing because the local configuration is missing: only versioned files are there, not the .env files.

The shared stash. A git stash made in one worktree shows up in all the others. With one worktree per feature I need it far less, but I’d rather know before running stash pop in the wrong place.

Housekeeping. Worktrees pile up. git worktree list, then git worktree remove once the PR is merged, is part of the ritual. Forget it, and you end up with five ghost folders.

It isn’t a sandbox. A worktree isolates the checkout, not secrets, not the network, not the side effects of a destructive command. The agent is still on my machine, with my privileges.

Why now

As soon as I hand part of the writing to an agent, I no longer have one developer facing one working tree: I have a small temporary team sharing my machine.

As I said earlier, my machine ends up being the equivalent of a server shared by several contributors.

Without an explicit split of workspaces, that team steps on itself.

As often, the conclusion is that the tool has to answer a need. I don’t regret missing out on worktrees while the context didn’t call for them.

Today, though, it’s a habit I wouldn’t go back on.

Martin Catty
I write about infrastructure, automation, security, AI, entrepreneurship and management.