Git worktrees are not an isolation boundary for coding agents

alchaplinsky1 pts0 comments

Git worktrees are not an isolation boundary for coding agents | Fletch<br>gitGit worktrees are not an isolation boundary for coding agents<br>Most tools that run AI coding agents in parallel give each one a git worktree. A worktree shares refs, config, stash and hooks with your real repository. Paste-and-run repros showing an agent can execute code in your main repo and rewrite your commit identity, plus benchmarks showing a properly isolated clone costs the same.<br>Alex ChaplinskyJul 30, 2026·12 min read

Give a coding agent its own git worktree, which is how most tools for running agents in parallel do it, and that agent can install a hook that runs on your machine the next time you commit in your real repository. It can rewrite the email your own commits are attributed to. It can pop another agent’s stash into its own tree.

None of that needs a bug or an exotic command. A worktree was never a boundary. It is a second working directory attached to one .git, and everything interesting lives in that .git.

Worktrees became the default here because they are the obvious answer. One command, no duplicated history, a second checkout in about a second. The pitch is isolation at nearly zero cost, and both halves of that turn out to be wrong: the isolation is much thinner than the phrase “isolated worktree” suggests, and the cost of doing it properly is the same. I had the cost part wrong in an earlier version of this post, until I measured it.

What a linked worktree actually shares

Inside a linked worktree, .git is not a directory. It is a file:

Terminal window$ cat ../my-worktree/.git

gitdir: /path/to/repo/.git/worktrees/my-worktree

Everything follows from that path. Git splits repository state into per-worktree state and common state, and it will tell you which is which:

Terminal window$ cd ../my-worktree

$ git rev-parse --git-dir # per-worktree

/path/to/repo/.git/worktrees/my-worktree

$ git rev-parse --git-common-dir # shared with the parent and every sibling

/path/to/repo/.git

Per-worktree state is a short list: HEAD, the index, ORIG_HEAD, and a few bisect and rebase files. Everything else resolves through --git-common-dir back into the original repository:

The object store (.git/objects), which is the point, and is safe to share.

Refs and branches (refs/heads, packed-refs). One namespace for every worktree.

The config (.git/config).

The stash (refs/stash).

Hooks (.git/hooks), which is where a shared directory becomes arbitrary code execution.

So the isolation is real, and it is narrow: your working directory, HEAD, and the index. Those are useful. They are not a boundary. A boundary would mean a process confined to the worktree cannot reach state outside it, and the second list is a catalogue of the ways it can. “Isolated worktree” gets used constantly, in tool descriptions and in advice threads, without ever saying which of the two lists is meant.

What that lets an agent do

Worst first. Each block creates its own repository, so you can paste them into one empty directory and run them in any order. Every line of output below is real, captured on git 2.50.1.

Execute code on your host

.git/hooks lives in the common directory. A hook installed from inside a worktree is therefore installed for the parent repository, and it runs as you, in the parent, the next time you run the triggering command.

Terminal windowgit init -q --initial-branch=main hook-demo

cd hook-demo

git commit -q --allow-empty -m init

git branch agent-work

git worktree add -q ../hook-agent agent-work

# The "isolated" worktree writes a hook into the parent's .git

cd ../hook-agent

cat > "$(git rev-parse --git-common-dir)/hooks/pre-commit" 'EOF'

#!/bin/sh

echo "*** hook running as $(whoami) in $(pwd) ***"

EOF

chmod +x "$(git rev-parse --git-common-dir)/hooks/pre-commit"

# Now you, in your own repository, make an ordinary commit

cd ../hook-demo

git commit -q --allow-empty -m "an ordinary commit"

# -> *** hook running as you in /path/to/hook-demo ***

"$(git rev-parse --git-common-dir)/hooks/pre-commit"

This is also why a worktree cannot be the isolation layer for a containerised agent. Handing a container a linked worktree means mounting the parent’s real .git, because that is where the worktree’s own state lives. A writable .git/hooks on the host side of that mount runs on the host, and the container stops meaning anything.

Rewrite who your commits are from

The config is shared, so git config inside a worktree writes the parent’s .git/config:

Terminal windowgit init -q --initial-branch=main id-demo

cd id-demo

git commit -q --allow-empty -m init

git branch agent-work

git worktree add -q ../id-agent agent-work

cd ../id-agent

git config user.email "agent@example.com"

cd ../id-demo

git config user.email

# -> agent@example.com

git config --show-origin user.email

# -> file:.git/config agent@example.com

git commit -q --allow-empty -m "a commit you made yourself"

git log -1 --format='%an '

# -> Your...

worktree agent commit hook config hooks

Related Articles