Git Worktrees — Isolated Parallel Workspaces
Worktrees give each agent its own directory with its own files — one shared git object store, zero write conflicts, complete isolation.
The Shared Working Tree Problem
Two agents, one repo, no worktrees. Agent A edits lesson-413.mdx. Agent B edits lesson-414.mdx. They seem to be working on different files — what could go wrong?
Three things:
First, both agents are operating in the same working directory. If Agent A runs a command that modifies git state (a checkout, a reset, a pull), it affects Agent B's working environment.
Second, if either agent touches a shared file — even accidentally — the other agent's staged changes may be overwritten or corrupted.
Third, and most critically in Knox's production environment: Agent Gateway — the always-on automation agent that runs scheduled cron jobs on the production server — runs cron jobs that include git operations. Those jobs run in the main working tree. A cron that runs a forced git operation (git checkout -f main, git reset --hard) mid-session will forcibly rewrite the working tree, destroying any uncommitted agent work.
How Worktrees Work
A git worktree is a linked copy of a repository — a separate directory where you can have a different branch checked out than the main directory. Multiple worktrees share one .git object store, so there is no duplication of history or objects. But each worktree has:
- Its own working directory (separate files on disk)
- Its own HEAD (independent branch)
- Its own staging area (index)
- Its own uncommitted state
When Agent A is in .claude/worktrees/agent-a/ and Agent B is in .claude/worktrees/agent-b/, they are writing to completely separate directories. Agent A's edits to lesson-413.mdx exist only in its worktree's directory. Agent B cannot see or overwrite these files because they are not in its directory.
The Four-Phase Lifecycle
Every worktree session follows the same lifecycle: setup → dispatch → verify → converge.
Setup happens in the main working tree before any workers start. git checkout main && git pull ensures you are starting from a clean, up-to-date baseline. Then create one worktree per same-repo agent with a unique branch per worktree.
Dispatch happens when you send workers to their worktrees. Each worker cds into its worktree directory and operates from there. The worker's first command inside the worktree is npm ci — a fresh worktree has its own working directory with no node_modules, so test and type-check commands fail immediately ("vitest: command not found") until dependencies are installed. After the install, all its writes, commits, and test runs happen in that isolated directory.
Verify happens within the worker's worktree before the worker reports done. npm run test in the worktree directory. npx tsc --noEmit in the worktree directory. The test results reflect the worktree's state, not the main working tree's.
Converge happens in the main working tree after all workers report PASS. The orchestrator cherry-picks each worker branch onto main, edits the shared registry files once, runs the full test suite, and removes the worktrees.
Creating and Managing Worktrees
# Setup: start clean
git checkout main && git pull
# Create worktrees (one per same-repo agent)
git worktree add .claude/worktrees/agent-a -b feat/agent-a
git worktree add .claude/worktrees/agent-b -b feat/agent-b
git worktree add .claude/worktrees/agent-c -b feat/agent-c
# Verify setup
git worktree list
# main (main worktree)
# .claude/worktrees/agent-a feat/agent-a
# .claude/worktrees/agent-b feat/agent-b
# Each worker installs dependencies in its own worktree first —
# a fresh worktree has NO node_modules, so tests fail without this
(cd .claude/worktrees/agent-a && npm ci)
(cd .claude/worktrees/agent-b && npm ci)
# Post-convergence cleanup (run AFTER cherry-picks)
git worktree remove .claude/worktrees/agent-a
git worktree remove .claude/worktrees/agent-b
# -D (force), not -d: cherry-pick rewrote the commits onto main with new
# SHAs, so the branch tips are not ancestors of main — -d refuses with
# "not fully merged"
git branch -D feat/agent-a feat/agent-b
# Confirm clean
git worktree list # should show only main
Write that lifecycle yourself for two same-repo agents — clean baseline, one branch-per-worktree, then the full teardown so nothing orphaned is left to pollute vitest.
The Orphaned Worktree Problem
There is a production incident pattern documented in Knox's production incident log: orphaned worktrees silently pollute vitest. When a parallel dispatch session ends without cleanup — the workers finished, the convergence happened, but nobody ran git worktree remove — the stale worktree directories remain on disk.
vitest scans for test files from the project root. The stale .claude/worktrees/ directories contain copies of the test files from when those worktrees were created. If those files have since been updated in the main worktree (different expected counts, different assertions), vitest may pick up both versions and assert contradictory values.
The symptom: tests that pass in the main directory fail when run from the project root. Or tests pass inconsistently — one run green, next run red, no code changes in between.
The prevention is simple: git worktree remove every worktree after convergence. Add it to the convergence checklist as a mandatory final step.
Cross-Repo Work Does Not Need Worktrees
The worktree discipline applies to same-repo parallel dispatch only. When tasks are in different git repositories — fixing CI in academy and fixing a bug in memory-service simultaneously — there is no shared working tree. Each repo has its own git state. Dispatch directly without worktrees.
This distinction matters practically. Knox's most common parallel dispatch pattern involves multiple repos: dispatch 3 cross-repo fixes simultaneously, no worktrees, no isolation overhead, just pure parallel execution. Worktrees are the same-repo solution to a same-repo problem.
What's Next
The next lesson maps every shared file in a typical repo and classifies it as hot (serialize), safe (parallelize freely), or caution (plan carefully). Understanding which files are hot before dispatch — not after a collision — is what makes registry-editing predictable.