Skip to main content
  1. Posts/

Git worktrees gave each Claude agent its own sandbox. And scattered my sessions.

··969 words·5 mins·
Nick Liu
Author
Nick Liu
Building infrastructure for Facebook Feed Ranking at Meta. Previously at Walmart, Twitter, AWS, and eBay. MS in Computer Science at Georgia Tech.
Table of Contents
Taming Claude Code Sessions - This article is part of a series.
Part 5: This Article
I run four or more Claude Code agents at once, and until recently they all shared one working tree. Two agents editing the same repo means one of them eventually builds against the other's half-finished changes. Git worktrees fix that cleanly. What nobody warned me about is that the fix multiplies a different problem I already had: forgetting which folder a session lives in.
Tested with Claude Code 2.1.x · macOS

The symptom
#

With several agents in one directory, the working tree is shared mutable state. Agent A refactors a partial, agent B runs the build, and B’s “failure” is really A’s work in flight. I had been dodging this by scoping agents to different subdirectories, which works until it does not.

The textbook answer is git worktrees: one repository, multiple checked-out directories, each on its own branch. Every agent gets an isolated tree, commits land in the same repo, and nothing stomps anything.

git worktree add ../myrepo.wt-feature -b feature/thing

One command. So I tried it on this blog’s repo, with a real task, expecting a five-minute win. The isolation worked as advertised. Everything around it needed more care than the tutorials let on, starting with a build that broke before the first agent typed a word.

Trap 1: your submodules arrive empty
#

The fresh worktree looked complete. It was not. First build:

ERROR error building site: assemble: failed to create page from pageMetaSource:
failed to extract shortcode: template for shortcode "keywordList" not found

That baffling shortcode error means the Hugo theme is missing, because the theme is a git submodule and a new worktree does not populate submodule content. git submodule status shows the tell, a - prefix on the commit hash. The fix is the usual incantation, run inside the worktree:

git submodule update --init --recursive

On my machine that took 22.6 seconds. Not painful once, but it is a per-worktree cost, and the failure mode points you at the theme, not at the actual cause.

Trap 2: dependencies do not follow you
#

node_modules is gitignored, so the worktree starts without it. Every worktree needs its own npm ci before lint or tests work. With a warm npm cache mine took 2.7 seconds; a cold cache costs minutes and disk. The general rule: everything your build needs that git does not track gets re-created per worktree. Budget for it before you spin up four of them.

Trap 3: one port, many dev servers
#

Two agents, two worktrees, both want a dev server. The second one dies immediately:

ERROR command error: server startup failed: listen tcp 127.0.0.1:1313:
bind: address already in use

Hugo defaults to port 1313 no matter which directory it runs from, and the hugo server docs show the flag you need. Assign each worktree its own port and put it in the launch command the agent sees:

hugo server --port 1314   # worktree A gets 1313, B gets 1314, ...

The same applies to any dev server with a default port. Fail fast here is the good outcome; some tools happily connect to the other worktree’s server instead, and then you are debugging ghosts.

The trap that follows you home: session scatter
#

The earlier posts in this series exist because I kept losing track of Claude Code sessions. Finding them again and naming tmux windows mostly solved it for one directory per project.

Worktrees quietly undo that. Claude Code keys its session history to the directory you launched from. On my machine, ~/.claude/projects/ holds one folder per absolute path, eleven of them already, and each worktree adds another. The session where the agent did the refactor now lives under myrepo.wt-feature, not under myrepo. Resume from the main checkout and that session is invisible. Multiply by four agents and a few abandoned worktrees, and “where did that conversation go” becomes a daily question.

flowchart TD
    A["myrepo/.git (one repository)"] --> B["myrepo/ (main)"]
    A --> C["myrepo.wt-auth/ (feature/auth)"]
    A --> D["myrepo.wt-tests/ (feature/tests)"]
    B --> E["sessions for path .../myrepo"]
    C --> F["sessions for path .../myrepo.wt-auth"]
    D --> G["sessions for path .../myrepo.wt-tests"]
    subgraph "~/.claude/projects/ (one bucket per directory)"
        E
        F
        G
    end

Two habits keep it under control:

First, a naming hook. My SessionStart hook auto-names every session after its project and branch, so even a session stranded in a worktree path carries a label like the branch it worked on. The hook lives in my dotfiles and reads the payload Claude Code hands it; part 2 of this series covers the hook mechanics.

Second, treat worktrees as disposable and delete them the moment the branch merges:

git worktree remove ../myrepo.wt-feature
git branch -d feature/thing

A removed worktree stops accumulating sessions, and the ones it produced stay findable by name instead of by path archaeology. For juggling the live agents themselves, the session picker from part 4 works unchanged; it lists sessions across directories, which suddenly matters a lot more.

Lessons
#

  • A fresh worktree is not a fresh clone: submodules arrive empty and gitignored artifacts do not exist. Script the init steps once and run them per worktree.
  • Default ports are global, worktrees are not. Assign a port per worktree before the second dev server starts, not after it fails.
  • Claude Code sessions are keyed to the launch directory. Every worktree is a new session bucket, so name sessions by branch or lose them to path archaeology.
  • Delete worktrees when the branch merges. The isolation is the point; the leftovers are pure liability.

References
#

  • git-worktree documentation
  • hugo server command reference (the --port flag)
  • Session naming hook: claude-name-session in my dotfiles, a SessionStart hook that titles sessions by project and branch
  • Error messages and timings captured on this machine (macOS, Hugo 0.163.3 extended, Claude Code 2.1.x) while writing this post
Taming Claude Code Sessions - This article is part of a series.
Part 5: This Article

Related

Running Several AI Coding Agents Without Losing Track

··876 words·5 mins
Once you're comfortable with AI coding agents, you start running several at once: one refactoring here, one writing tests there, one stuck waiting for your approval. Keeping them straight is its own little skill. Taming Claude Code Sessions · Part 4 of 5 1 2 3 4 5 🧪 Tested with Claude Code 2.1.x · macOS / Linux Here are two ways to do it: a lightweight tmux plugin, and (briefly) dedicated “AI terminal” apps.

Make tmux Show What Each Window Is Doing

··780 words·4 mins
If you use tmux, you've hit this: ten windows open and they're all named `zsh` or `node`. Which one had your AI agent running? No idea. Let's make tmux label windows usefully. Taming Claude Code Sessions · Part 3 of 5 1 2 3 4 5 🧪 Tested with Claude Code 2.1.x · macOS / Linux New to tmux? It’s a “terminal multiplexer”: it splits one terminal into many windows and panes that survive disconnects. The only vocabulary you need here: a window is like a browser tab inside tmux; the bar at the bottom lists them. The prefix is the key you press before a tmux command, commonly Ctrl+b (mine is Ctrl+a). Why everything is named zsh # By default tmux has a setting called automatic-rename turned on. It renames each window after whatever program is running in it. A shell? zsh. A Node program (like Claude Code)? node. Helpful in theory, useless when everything collapses to the same word.

Stop Losing Your Claude Code Conversations

··624 words·3 mins
You're deep in a great Claude Code conversation. You close the terminal. The next day you want to pick up where you left off… and you can't find it. Sound familiar? Let's fix that. Taming Claude Code Sessions · Part 1 of 5 1 2 3 4 5 🧪 Tested with Claude Code 2.1.x · macOS / Linux What is a “session,” and where does it go? # Every time you run claude, you start a session, one conversation, with its full history. When you quit, that history doesn’t vanish. Claude Code saves it to disk, organized per project folder, here: