Git Worktrees Tutorial

Intro

Git Worktrees: Run Multiple Branches Simultaneously

TLDR: Git worktrees let you have multiple branches checked out at the same time in separate directories, all sharing the same git repository. Perfect for running multiple coding agents in parallel without interference.

Why Worktrees?

Normally, one git repo = one working directory = one branch at a time. Switching branches changes files for everyone. With worktrees, you can have multiple branches active simultaneously, each in its own folder.

Basic Commands

See current worktrees:

git worktree list

Create worktree with a new branch:

git worktree add ../path-to-new-folder -b new-branch-name

Create worktree from existing branch:

git worktree add ../path-to-new-folder existing-branch-name

Remove a worktree:

git worktree remove /path/to/worktree

Key Concepts

Typical Workflow

  1. Create worktrees for each task: git worktree add ../agent-feature-x -b feature-x
  2. Open separate terminals, cd into each worktree
  3. Work independently in each (or run agents like claude)
  4. Commit changes in each worktree to its branch
  5. Merge branches back to main: git merge feature-x
  6. Clean up: git worktree remove ../agent-feature-x and git branch -d feature-x

Tutorial

Below is a dialog I had in the Solve It platform so I could walk through the process of learning about git worktrees.


I want to learn about git worktrees. I've never used them before. I am familiar with basic git commands such as

git checkout -b <branch name>
git add
git commit -m
git push

The reason I want to learn about git worktrees is because I want to run coding agents on the same project.

Scenarios I am interested in learning to do are:

Cloning a Demo Repo

Here is the documentation on git worktrees:

We will begin by cloning a demo repo: https://github.com/octocat/Hello-World.

I want to have a solid tutorial on the basics of git worktrees for my primary use case (running multiple agents in parallel on different branches). After cloning the repo we will use Python os to change the working directory to the root of the cloned repo.

We will simulate an agent making changes on the primary worktree and then have two other worktrees. This is why I remove these three folders below for a fresh slate each time this notebook is run.

!rm -rf Hello-World
!rm -rf agent-bugfix1
!rm -rf agent-bugfix2

I would like for you to walk me through a tutorial on git worktrees in this notebook. Ready to begin?

Great! I'm ready to help you learn git worktrees. This is a really useful feature for your use case of running multiple agents in parallel on different branches.

Let's start by setting up the demo repository. First, run those cleanup and clone commands:

!rm -rf Hello-World agent-bugfix1 agent-bugfix2
!git clone https://github.com/octocat/Hello-World

After that runs successfully, we'll change into the repository directory. Let me know once you've run this and we'll take the next step!

!rm -rf Hello-World agent-bugfix1 agent-bugfix2
!git clone https://github.com/octocat/Hello-World
Cloning into 'Hello-World'...
remote: Enumerating objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13 (from 1)
Receiving objects:   7% (1/13)
Receiving objects:  15% (2/13)
Receiving objects:  23% (3/13)
Receiving objects:  30% (4/13)
Receiving objects:  38% (5/13)
Receiving objects:  46% (6/13)
Receiving objects:  53% (7/13)
Receiving objects:  61% (8/13)
Receiving objects:  69% (9/13)
Receiving objects:  76% (10/13)
Receiving objects:  84% (11/13)
Receiving objects:  92% (12/13)
Receiving objects: 100% (13/13)
Receiving objects: 100% (13/13), done.
import os
os.chdir('Hello-World')
# Verify we're in the right place
!pwd
!ls
/app/data/work_tree_demo/Hello-World
README
!git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
!git branch
* master

Let's start from the very beginning. Don't skip things I should know.

Perfect! Let's start with the fundamentals of git worktrees.

What is a Git Worktree?

Normally, a git repository has one working directory where you check out branches. If you want to work on a different branch, you have to switch branches with git checkout, which changes all the files in your working directory.

A worktree lets you have multiple working directories for the same repository. Each worktree can have a different branch checked out. This means you can:

  • Work on multiple branches simultaneously without switching
  • Keep each branch's files completely separate
  • Perfect for your use case: run multiple agents on different branches in parallel!

The Main Worktree

Right now, you're in what's called the main worktree (or primary worktree). This is the original Hello-World directory you cloned. Let's see what we have:

!git worktree list

Run this command. It will show you all worktrees associated with this repository. Right now, you should only see one - the main worktree on the master branch.

!git worktree list
/app/data/work_tree_demo/Hello-World  7fd1a60 [master]

So the first thing I notice is I have been using git worktrees all along (the primary branch), right? I don't think of it as its own "tree/folder". I just think of it as the root of the repo.

Exactly! That's a great observation. You've been using a worktree all along - you just didn't know it had that name!

The main worktree is indeed just the root of your repo. When git added the worktree feature, they didn't change how the main repository works at all. They just made it possible to create additional worktrees that link back to the same git repository.

So the mental model is:

  • Before worktrees existed: You had one folder with your repo, and you'd git checkout to switch branches
  • With worktrees: That original folder is still there (the main worktree), but now you can create additional folders that are linked to the same .git directory

Think of it like this: the main worktree is your "home base," and additional worktrees are like "branch offices" - they all share the same git history and configuration, but each can work on different branches independently.

Ready to create your first additional worktree?

Yep. Please cover the commands for creating a new branch as well as assuming I'm already on a branch. For example the Claude Code docs say

# Create a new worktree with a new branch 
git worktree add ../project-feature-a -b feature-a

# Or create a worktree with an existing branch
git worktree add ../project-bugfix bugfix-123

Great! Let's break down both scenarios:

Creating a Worktree with a NEW Branch

git worktree add ../agent-bugfix1 -b bugfix1

This does two things:

  1. Creates a new branch called bugfix1 (the -b flag means "create branch")
  2. Creates a new directory at ../agent-bugfix1 with that branch checked out

The new branch starts from wherever you currently are (in this case, master).

Creating a Worktree from an EXISTING Branch

git worktree add ../agent-bugfix2 bugfix2

This assumes the branch bugfix2 already exists. It:

  1. Creates a new directory at ../agent-bugfix2
  2. Checks out the existing bugfix2 branch there

No -b flag needed since the branch already exists.

Key Points

  • The path (../agent-bugfix1) is where the new worktree folder will be created (relative to your current location)
  • You can only check out a branch in one worktree at a time - git won't let you have the same branch active in multiple worktrees simultaneously

Ready to try creating your first worktree?

!git branch
* master
!git checkout -b bugfix-1
Switched to a new branch 'bugfix-1'
!git worktree add ../agent-bugfix1 bugfix-1
Preparing worktree (checking out 'bugfix-1')
fatal: 'bugfix-1' is already checked out at '/app/data/work_tree_demo/Hello-World'

I guess this is what you said above about one branch per worktree?

Exactly! You've just hit that restriction. Git is telling you: "Hey, bugfix-1 is already checked out in the main worktree (/app/data/Hello-World), so I can't check it out in another worktree too."

This is actually a safety feature - it prevents conflicts from having the same branch active in multiple places at once.

To fix this, you have two options:

Option 1: Switch your main worktree back to master, then create the worktree with bugfix-1:

!git checkout master
!git worktree add ../agent-bugfix1 bugfix-1

Option 2: Create the worktree with a new branch instead:

!git worktree add ../agent-bugfix1 -b bugfix-1-agent

Which approach makes more sense for your use case of running agents in parallel?

!git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
!git branch
  bugfix-1
* master
!git worktree add ../agent-bugfix1 bugfix-1
Preparing worktree (checking out 'bugfix-1')
HEAD is now at 7fd1a60 Merge pull request #6 from Spaceghost/patch-1

Like that.

Perfect! Now you have your first additional worktree. Let's see what you've got:

!git worktree list

Run this to see both worktrees - your main one on master and the new one on bugfix-1.

!git worktree list
/app/data/work_tree_demo/Hello-World    7fd1a60 [master]
/app/data/work_tree_demo/agent-bugfix1  7fd1a60 [bugfix-1]
pwd
'/app/data/work_tree_demo/Hello-World'
ls
README
os.chdir('..')
pwd
'/app/data/work_tree_demo'
ls
Hello-World/  agent-bugfix1/  git_work_trees.ipynb

So I see the two worktrees beside eachother. They also just look like two ordinary folders. But they are the same git repo? That's a little confusing to me. Maybe just because I usally think of a git repo as a single folder. Can you explain what's happening here? I assume they are not just 2 different git clones. You said they shared the same .git

Great question! This is the key insight about worktrees.

You're right - they're not two separate clones. They share the same .git directory. Let's look:

!ls -la Hello-World/.git
!ls -la agent-bugfix1/.git

Run both of these. You'll see something interesting about the .git in the worktree folder - it's different from the main one.

!ls -la Hello-World/
total 16
drwxrwxr-x 3 solveit solveit 4096 Jan  3 20:47 .
drwxrwxr-x 4 solveit solveit 4096 Jan  3 20:47 ..
drwxrwxr-x 9 solveit solveit 4096 Jan  3 20:47 .git
-rw-rw-r-- 1 solveit solveit   13 Jan  3 20:47 README
!ls -la agent-bugfix1/
total 16
drwxrwxr-x 2 solveit solveit 4096 Jan  3 20:47 .
drwxrwxr-x 4 solveit solveit 4096 Jan  3 20:47 ..
-rw-rw-r-- 1 solveit solveit   74 Jan  3 20:47 .git
-rw-rw-r-- 1 solveit solveit   13 Jan  3 20:47 README

and?

Notice the size difference!

  • Hello-World/.git is a directory (4096 bytes)
  • agent-bugfix1/.git is a file (59 bytes)

Let's see what's inside that file:

!cat agent-bugfix1/.git

This will show you the secret - it's just a pointer back to the main .git directory!

!cat agent-bugfix1/.git
gitdir: /app/data/work_tree_demo/Hello-World/.git/worktrees/agent-bugfix1

so?

There it is! The .git file in agent-bugfix1 is just a pointer that says "my real git data is at /app/data/Hello-World/.git/worktrees/agent-bugfix1".

So here's what's happening:

  • Hello-World/.git/ contains the actual git repository (all commits, history, config, etc.)
  • agent-bugfix1/.git is just a tiny text file pointing back to the main .git
  • All worktrees share the same commit history, branches, and remotes

This means:

  • If you commit in agent-bugfix1, that commit goes into the shared repository
  • You can see branches created in one worktree from all other worktrees
  • Much more disk-efficient than cloning multiple times!

Want to see this in action by making a change in the worktree?

Yes, I think this will all become more clear after running some examples and inspecting. I guess we should begin by using os.chdir

Exactly! Let's change into the worktree directory:

os.chdir('agent-bugfix1')

After you run that, check where you are and what branch you're on:

!pwd
!git branch
os.chdir('agent-bugfix1')
!pwd
!git branch
/app/data/work_tree_demo/agent-bugfix1
* bugfix-1
+ master

ok

Perfect! You're now in the agent-bugfix1 worktree on the bugfix-1 branch (the * shows which branch is active).

Let's make a change here to simulate what an agent might do. Create a new file:

!echo "Bug fix #1: Fixed critical issue" > bugfix1.txt
!git status

This will show you the new file as untracked. What do you think will happen if we look at git status back in the main worktree?

!echo "Bug fix #1: Fixed critical issue" > bugfix1.txt
!git status
On branch bugfix-1
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	bugfix1.txt

nothing added to commit but untracked files present (use "git add" to track)
!git add bugfix1.txt
! git commit -m "Bug Fix 1"
[bugfix-1 6b2502d] Bug Fix 1
 1 file changed, 1 insertion(+)
 create mode 100644 bugfix1.txt

Now what

Great! You've committed a change on the bugfix-1 branch in the worktree. Now let's go back to the main worktree and see what it looks like there:

os.chdir('../Hello-World')

Then check:

!pwd
!git branch
!ls

What branch do you think you'll be on, and will you see the bugfix1.txt file?

os.chdir('../Hello-World')
!pwd
!git branch
!ls
/app/data/work_tree_demo/Hello-World
+ bugfix-1
* master
README

Now it's not there. Because I'm on the master branch, right?

Exactly! You're on the master branch, and bugfix1.txt only exists on the bugfix-1 branch.

But notice something important - you can see bugfix-1 in the branch list (with the + symbol). That + means it's checked out in another worktree. The commit you made in the agent-bugfix1 worktree is now part of the shared repository.

Want to verify that commit exists? Try:

!git log bugfix-1 --oneline

This will show you the commit history of the bugfix-1 branch, including your new commit, even though you're currently on master.

!git log bugfix-1 --oneline
6b2502d (bugfix-1) Bug Fix 1
7fd1a60 (HEAD -> master, origin/master, origin/HEAD) Merge pull request #6 from Spaceghost/patch-1
7629413 New line at end of file. --Signed off by Spaceghost
553c207 first commit

And I would get an error if I tried to checkout that branch on this worktree because the other worktree already has that branch checked out, correct?

Exactly right! Let's verify that:

!git checkout bugfix-1

Try it and see what error message git gives you.

!git checkout bugfix-1 
fatal: 'bugfix-1' is already checked out at '/app/data/work_tree_demo/agent-bugfix1'

Next I am going to check out a branch on the primary worktree and edit the README. Then I will create a bugfix-2 worktree and branch in one go.

!git branch
+ bugfix-1
* master
!git checkout -b update-readme
Switched to a new branch 'update-readme'
!echo "Updated by agent" >> README
!git status
On branch update-readme
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   README

no changes added to commit (use "git add" and/or "git commit -a")
!git add README
!git commit -m 'update the README'
!git status
[update-readme e346851] update the README
 1 file changed, 1 insertion(+)
On branch update-readme
nothing to commit, working tree clean
!git branch
+ bugfix-1
  master
* update-readme
!git worktree list
/app/data/work_tree_demo/Hello-World    e346851 [update-readme]
/app/data/work_tree_demo/agent-bugfix1  6b2502d [bugfix-1]
!git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
!git worktree add ../agent-bugfix2 -b bugfix-2
Preparing worktree (new branch 'bugfix-2')
HEAD is now at 7fd1a60 Merge pull request #6 from Spaceghost/patch-1
os.chdir('../agent-bugfix2/')
!git branch
+ bugfix-1
* bugfix-2
+ master
  update-readme
ls
README
!echo "This is a second bugfix" > bugfix2.txt
!git status
On branch bugfix-2
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	bugfix2.txt

nothing added to commit but untracked files present (use "git add" to track)
!git add bugfix2.txt
!git commit -m 'fixing the second bug'
[bugfix-2 bdc2947] fixing the second bug
 1 file changed, 1 insertion(+)
 create mode 100644 bugfix2.txt
os.chdir('../Hello-World/')
!git checkout master
Already on 'master'
Your branch is up to date with 'origin/master'.

Okay so I think I have simulated "three agents" working on three different worktrees right?

Yes, exactly! You've simulated three agents working independently:

  1. Main worktree (Hello-World/) - worked on update-readme branch, modified README
  2. agent-bugfix1 - worked on bugfix-1 branch, created bugfix1.txt
  3. agent-bugfix2 - worked on bugfix-2 branch, created bugfix2.txt

All three are completely independent - they didn't interfere with each other, and each has their own changes committed to their own branch.

Let's verify all three branches exist with their changes:

!git branch -v

This will show all branches with their latest commit messages. What do you want to explore next - maybe merging these branches, or cleaning up worktrees?

!git branch -v
+ bugfix-1      6b2502d Bug Fix 1
+ bugfix-2      bdc2947 fixing the second bug
* master        7fd1a60 Merge pull request #6 from Spaceghost/patch-1
  update-readme e346851 update the README

Now lets merge these changes into master.

! git branch
!pwd
+ bugfix-1
+ bugfix-2
* master
  update-readme
/app/data/work_tree_demo/Hello-World
!git merge update-readme
!git merge bugfix-1 --no-edit
!git merge bugfix-2 --no-edit
Updating 7fd1a60..e346851
Fast-forward
 README | 1 +
 1 file changed, 1 insertion(+)
Merge made by the 'ort' strategy.
 bugfix1.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 bugfix1.txt
Merge made by the 'ort' strategy.
 bugfix2.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 bugfix2.txt
!git status
On branch master
Your branch is ahead of 'origin/master' by 5 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
!git log
commit d2d05c3743e8084e902ac7612f71a28a0090b148 (HEAD -> master)
Merge: 35f9e54 bdc2947
Author: Solveit <nobody@fast.ai>
Date:   Sat Jan 3 20:48:52 2026 +0000

    Merge branch 'bugfix-2'

commit 35f9e54c8b4c84501f1766fe56997fcf240477c0
Merge: e346851 6b2502d
Author: Solveit <nobody@fast.ai>
Date:   Sat Jan 3 20:48:52 2026 +0000

    Merge branch 'bugfix-1'

commit bdc29472b789988e5d02504852149d193ed11300 (bugfix-2)
Author: Solveit <nobody@fast.ai>
Date:   Sat Jan 3 20:48:42 2026 +0000

    fixing the second bug

commit e346851cd6c5bfd473145544bd63026580e3ac04 (update-readme)
Author: Solveit <nobody@fast.ai>
Date:   Sat Jan 3 20:48:24 2026 +0000

    update the README

commit 6b2502d909538360005db6ea0e882c0657355ecb (bugfix-1)
Author: Solveit <nobody@fast.ai>
Date:   Sat Jan 3 20:48:10 2026 +0000

    Bug Fix 1

commit 7fd1a60b01f91b314f59955a4e4d4e80d8edf11d (origin/master, origin/HEAD)
Merge: 553c207 7629413
Author: The Octocat <octocat@nowhere.com>
Date:   Tue Mar 6 15:06:50 2012 -0800

    Merge pull request #6 from Spaceghost/patch-1
    
    New line at end of file.

commit 762941318ee16e59dabbacb1b4049eec22f0d303
Author: Johnneylee Jack Rollins <Johnneylee.rollins@gmail.com>
Date:   Tue Sep 13 21:42:41 2011 -0700

    New line at end of file. --Signed off by Spaceghost

commit 553c2077f0edc3d5dc5d17262f6aa498e69d6f8e
Author: cameronmcefee <cameron@github.com>
Date:   Wed Jan 26 11:06:08 2011 -0800

    first commit

Now what? Worktree cleanup?

Yes! Now let's clean up the worktrees. Once you've merged the changes, you typically don't need those separate worktrees anymore.

First, let's see what worktrees you have:

!git worktree list

Then to remove a worktree, you use:

git worktree remove <path>

Try removing one of them. Which one do you want to remove first?

!git worktree list
/app/data/work_tree_demo/Hello-World    d2d05c3 [master]
/app/data/work_tree_demo/agent-bugfix1  6b2502d [bugfix-1]
/app/data/work_tree_demo/agent-bugfix2  bdc2947 [bugfix-2]
!git worktree remove /app/data/work_tree_demo/agent-bugfix1
!git worktree remove /app/data/work_tree_demo/agent-bugfix2 
!git worktree list
/app/data/work_tree_demo/Hello-World  d2d05c3 [master]

And done?

Almost! The worktree directories are removed, but the branches still exist. Check:

!git branch

You'll see bugfix-1, bugfix-2, and update-readme are still there. Do you want to delete those branches too, or keep them?

!git branch
  bugfix-1
  bugfix-2
* master
  update-readme
!git branch -D   bugfix-1   bugfix-2   update-readme
Deleted branch bugfix-1 (was 6b2502d).
Deleted branch bugfix-2 (was bdc2947).
Deleted branch update-readme (was e346851).

Okay I think I have a good understanding of the basics now. I would run my agent from within the git worktree. So I would cd into each worktree directory and run claude to open claude coding agent etc. I could have multiple terminal tabs opened, each with their own worktree. The reason I could not do this before was because I was only ever using the primary worktree which only supports one branch at a time (checking out a branch would change it in all terminals).

Exactly! You've got it perfectly.

Before worktrees:

  • One repo = one branch at a time
  • All terminal tabs see the same branch
  • Switching branches in one terminal affects all others
  • Can't run multiple agents in parallel on different branches

With worktrees:

  • One repo = multiple worktrees = multiple branches simultaneously
  • Each terminal tab can cd into a different worktree
  • Each worktree is completely independent
  • Perfect for running multiple agents in parallel!

So your workflow would be:

  1. Create worktrees for each agent task: git worktree add ../agent-feature-x -b feature-x
  2. Open separate terminals, cd into each worktree
  3. Run your agent (e.g., claude) in each terminal
  4. Each agent works independently on its own branch
  5. When done, merge branches back to main and clean up worktrees

The key benefit: no interference - each agent has its own isolated workspace with its own branch checked out.

Is there anything else about worktrees you'd like to explore before wrapping up this tutorial?

Will files that are not committed to git on the primary worktree get copied to the other worktrees? Let's check that. One other thing I was curious about.

!echo "This is not committed" > temp_file.txt
!git status
On branch master
Your branch is ahead of 'origin/master' by 5 commits.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	temp_file.txt

nothing added to commit but untracked files present (use "git add" to track)
!git worktree list
/app/data/work_tree_demo/Hello-World  d2d05c3 [master]
!git branch
* master
!git worktree add ../new-feature -b new-feature
Preparing worktree (new branch 'new-feature')
HEAD is now at d2d05c3 Merge branch 'bugfix-2'
os.chdir('../new-feature/')
!git branch
!git status
!ls
+ master
* new-feature
On branch new-feature
nothing to commit, working tree clean
README	bugfix1.txt  bugfix2.txt

Not copied.

Correct! Untracked/uncommitted files are not copied to new worktrees.

Each worktree only contains:

  • Files that are committed to the branch it has checked out
  • Any new files you create in that specific worktree

So in your case:

  • temp_file.txt exists only in the main worktree (untracked)
  • The new-feature worktree doesn't have it
  • The new-feature worktree has README, bugfix1.txt, and bugfix2.txt because those were committed to master (which new-feature branched from)

This is actually perfect for your agent use case - each agent's workspace is clean and only contains committed code. Any temporary files, logs, or work-in-progress that an agent creates stays isolated in its own worktree.

Does this complete your understanding of worktrees, or is there anything else you'd like to test?

!git worktree list
/app/data/work_tree_demo/Hello-World  d2d05c3 [master]
/app/data/work_tree_demo/new-feature  d2d05c3 [new-feature]
!git worktree remove /app/data/work_tree_demo/new-feature