Simplifying Git Worktrees


Worktrees weren’t something that clicked for me right away. It seemed like a niche Git concept, and as a developer I was accustomed to the traditional workflow of discrete checkouts and branches. For some reason nothing I read made understanding worktrees any simpler. It also wasn’t obvious why I’d want multiple agents running at the same time when they still required constant babysitting.

That changed. Agents require less babysitting now, and I kept running into the same pattern: wanting to multitask across features and tasks related to my codebase (like PR review) while an agent was mid-work on something else. That led me back to worktrees. And once it clicked, it’s truly simple.

A worktree is a tracked copy of your repo

That’s it. Git copies (a literal copy/paste on the filesystem) your project into another directory and tracks it. Each copy gets its own branch. They share the same Git history. No cloning, no redundancy.

~/projects/
  my-app/            ← your repo
  fix-auth/          ← worktree (just another copy, its own branch)

Both are full working copies. Separate editors, separate terminals, separate everything. They don’t touch each other.

The worktree is created with: git worktree add -b fix-auth ../fix-auth main. You would cd to the directory and spin up a new claude/codex/opencode/etc agent to focus on that work.

Agentic coding

Tools like Claude Code and Codex let you run AI agents that read files, write code, run tests, and build up context over time. The deeper an agent gets into a task, the more valuable that context becomes. And the more often you find agents continuously running over longer periods of time. You don’t want to interrupt it.

I’m actively using worktrees to:

  1. Develop multiple features in parallel
  2. Review colleague PRs without breaking stride on my own work. Claude Code ships solid code review tooling like /code-review <pr-link> and /simplify that work best when they have an isolated checkout (the PR branch) to operate on. Spin up a worktree, point the agent at it, review the PR. Your main work never stops.
  3. Bonus: sometimes I’ll treat a new UI feature like a slot machine and launch a bunch of different worktree agents with varying instructions to see how different things might look.

Example

Say you’re on your main checkout and an agent is mid-feature. A patch comes in that needs fixing now. Your options used to be: stash, switch branches, fix, switch back, pop stash, hope for the best. The agent loses all its context. You lose momentum on both tasks.

  • Sometimes I’d just clone the whole repo into a new directory to get that isolation manually. A worktree is the same idea, but Git tracks it for you.

With a worktree:

git worktree add -b hotfix-csrf ../hotfix-csrf main

New directory, new branch, off main. Spin up another agent in there. Fix the patch there. Your first agent never stops. It doesn’t know anything happened.

This scales to whatever you need:

git worktree add -b feat/notifications ../notifications main
git worktree add -b feat/export-csv ../export-csv main
git worktree add -b fix/timeout-bug ../timeout-bug main
~/projects/
  my-app/              ← main checkout, agent working on current feature
  notifications/       ← agent 2
  export-csv/          ← agent 3
  timeout-bug/         ← agent 4

Four agents, four tasks, zero interference. You can also create a worktree just to review someone else’s PR without stopping anything you’re doing:

git fetch origin feat/new-parser
git worktree add --detach ../review-parser origin/feat/new-parser

Detached means no branch gets created. You just get their code in a folder to read and test.

The commands

Five total.

Create (new branch off main):

git worktree add -b <branch> <path> main

Create detached (for review, no branch):

git worktree add --detach <path> origin/<branch>

List:

git worktree list

Remove:

git worktree remove <path>

Prune (clean up stale entries, in case you manually deleted worktree directories):

git worktree prune

One thing worth keeping consistent: name the branch and the directory the same. If the branch is fix-auth and the directory is auth-stuff, it gets confusing fast when you have five of these running.

Aliases to cut the typing

I’ve packaged up my own setup you can check out: worktree-aliases.

The pattern is almost always identical: branch off main, put it in a sibling directory, same name for both. Aliases handle that.

WT_DIR="${WT_DIR:-..}"

wt()      { git worktree add -b "$1" "$WT_DIR/$1" "${2:-main}"; }
wtr()     { git fetch origin "$1" && git worktree add --detach "$WT_DIR/${2:-${1//\//-}}" "origin/$1"; }
wtrm()    { git worktree remove "$WT_DIR/$1" && [ "${2:-}" != "-k" ] && git branch -d "$1" 2>/dev/null; }
wtl()     { git worktree list; }
wtcd()    { cd "$WT_DIR/$1"; }
wtprune() { git worktree prune -v; }

Now it’s:

wt fix-auth          # create worktree + branch off main
cd ../fix-auth       # cd into it
claude               # this session will focus on this specific fix

# work, commit, push, merge
wtrm fix-auth        # remove worktree + delete branch

wtr is for review. wtrm -k keeps the branch if you don’t want it deleted.

That’s the whole thing

Worktrees are tracked copies of your repo in sibling directories. They exist so you can work on multiple things without anything overlapping. They became relevant because agentic tools made parallel development a normal workflow instead of a theoretical one. Git already prevents you from doing anything stupid with them (duplicate branch checkouts, name collisions).

No special tooling required. The native commands plus a few aliases cover it.