跳转到内容

Isolation & Worktree

此内容尚不支持你的语言。

When multiple agents work on the same codebase simultaneously, they need isolation to avoid stepping on each other’s changes. Claude Code uses git worktrees as its primary isolation mechanism — each agent gets its own working copy of the repository, sharing the same git history but with independent file systems.

The alternatives and why they were rejected:

ApproachProblem
Same directoryFile conflicts between concurrent agents
Full cloneDisk space, slow, separate git history
Docker/containerHeavy, complex setup, not universally available
Git worktree✅ Lightweight, shared history, independent files

Git worktrees are a native git feature (git worktree add) that creates a new working directory linked to the same repository. Agents can independently modify files, create branches, and commit without affecting each other.

Agent definitions support two isolation modes, declared in BaseAgentDefinition:

src/tools/AgentTool/loadAgentsDir.ts
isolation?: 'worktree' | 'remote'
ModeAvailabilityDescription
worktreeAll usersLocal git worktree on disk
remoteAnthropic internal onlyRemote execution in Claude Code Remote (CCR)

The remote mode is gated at parse time:

// src/tools/AgentTool/loadAgentsDir.ts — AgentJsonSchema
isolation: (process.env.USER_TYPE === 'ant'
? z.enum(['worktree', 'remote'])
: z.enum(['worktree'])
).optional(),

External users can only use worktree. Attempting to set remote in an agent definition will fail validation.

sequenceDiagram
participant L as Leader Agent
participant W as Worktree System
participant G as Git
participant A as Agent (in worktree)
L->>W: Spawn agent with isolation: 'worktree'
W->>G: git worktree add <path> <branch>
G-->>W: Worktree created at <path>
W->>A: Start agent with cwd = worktree path
Note over A: Agent works independently<br/>in its own working copy
A->>A: Read, Edit, Bash, Commit...
A-->>L: Report results
L->>W: Cleanup / Kill agent
W->>G: git worktree remove --force <path>
G-->>W: Worktree removed

Each team member can have its own worktree, stored in the team configuration:

// TeamMember in teamHelpers.ts
type TeamMember = {
agentId: string
worktreePath?: string // Path to this member's worktree
// ...
}

When a team is disbanded or a member is killed, the worktree must be cleaned up. The destroyWorktree() function handles this carefully:

// src/utils/swarm/teamHelpers.ts — destroyWorktree (conceptual)
async function destroyWorktree(worktreePath: string): Promise<void> {
// 1. Read .git file to find the main repository
// (worktree .git is a file, not a directory: "gitdir: /path/to/main/.git/worktrees/...")
const gitContent = await readFile(join(worktreePath, '.git'), 'utf-8')
// 2. Extract main repo path from gitdir reference
const mainRepoPath = resolveMainRepo(gitContent)
// 3. Use git worktree remove for clean removal
try {
await exec(`git worktree remove --force ${worktreePath}`, { cwd: mainRepoPath })
} catch {
// 4. Fallback: force remove the directory
await exec(`rm -rf ${worktreePath}`)
}
}

When the fork mechanism creates a child in an isolated worktree, a special notice is injected into the child’s context:

src/tools/AgentTool/forkSubagent.ts
export function buildWorktreeNotice(parentCwd: string, worktreeCwd: string): string {
return `You've inherited the conversation context above from a parent agent
working in ${parentCwd}. You are operating in an isolated git worktree at
${worktreeCwd} — same repository, same relative file structure, separate
working copy.
Paths in the inherited context refer to the parent's working directory;
translate them to your worktree root.
Re-read files before editing if the parent may have modified them since
they appear in the context.
Your changes stay in this worktree and will not affect the parent's files.`
}

This notice addresses three critical issues:

  1. Path translation — inherited paths point to the parent’s directory, not the worktree
  2. Stale context — files read in the parent’s context may have changed
  3. Isolation guarantee — changes won’t affect the parent’s working copy

When teams are cleaned up, all worktrees must be destroyed before directories are removed:

flowchart TD
A[cleanupTeamDirectories<br/>teamName] --> B[Read team config.json]
B --> C[For each member<br/>with worktreePath]
C --> D[destroyWorktree<br/>member.worktreePath]
D --> E{Success?}
E -->|yes| F[Continue]
E -->|no| G[Log error, continue]
F --> H[Remove task directories<br/>for each member]
H --> I[Delete team config file]

Session-level cleanup (cleanupSessionTeams) runs when a session ends, ensuring no orphaned worktrees remain:

// Cleanup finds all team files for the session and destroys their worktrees
async function cleanupSessionTeams(sessionId: string): Promise<void> {
const teamFiles = await findTeamFilesForSession(sessionId)
for (const teamFile of teamFiles) {
await cleanupTeamDirectories(teamFile.name)
}
}

The worktree system also supports standalone sessions (not just team members). getCurrentWorktreeSession() in src/utils/worktree.ts tracks worktree metadata for the current session:

// Used in prompts.ts for the statusLine agent input
type WorktreeSession = {
name: string // Worktree name/slug
path: string // Full path to worktree
branch?: string // Git branch
original_cwd: string // Directory before entering worktree
original_branch?: string
}

This metadata flows into the status line configuration, letting users see which worktree they’re in and quickly navigate back.

Worktree paths are validated to prevent path traversal:

  • Paths must be absolute
  • Paths cannot escape the project root
  • The .git file in worktrees is read to validate it points to a legitimate repository

The validateMemoryPath() function in src/memdir/paths.ts shows the same pattern of path validation used throughout the system — rejecting relative paths, root paths, null bytes, and UNC paths.