Files

180 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- Worktree
- 文件隔离
- 多Agent隔离
create time: 2026-06-09 22:30
---
# Worktree 隔离 - Git Worktree 实现文件级隔离
## 概述
揭秘 Claude Code 的 git worktree 隔离机制:子 Agent 如何获得独立工作空间,worktree 创建/销毁生命周期、路径命名规则和安全防护。
## 正文
### 为什么需要文件级隔离
多 Agent 并行工作时,共享同一工作目录会导致三类冲突:
1. **写入冲突**:两个 Agent 同时编辑 `config.ts`,后写的覆盖前写的
2. **状态干扰**:Agent A 的测试依赖某个环境状态,Agent B 的修改破坏了它
3. **不可区分**:半完成的修改混在一起,无法分辨哪些是哪个 Agent 的
Git worktree 是 git 原生的解决方案——在同一个仓库中创建多个独立工作目录,每个在自己的分支上。
### 目录结构与命名规则
Worktree 文件统一存放在仓库根目录下的 `.claude/worktrees/`:
```text
<repo-root>/
├── .claude/
│ └── worktrees/
│ ├── fix-auth-bug/ # worktree 工作目录
│ │ ├── .git # 指向主仓库的链接文件
│ │ └── src/... # 独立的文件系统视图
│ └── add-dark-mode/ # 另一个 worktree
│ └── ...
├── src/ # 主工作目录(不受影响)
└── .git/ # 主仓库
```
分支命名规则为 `worktree/<slug>`,其中 slug 由 `validateWorktreeSlug()` 校验:每个 `/` 分隔的段只允许字母、数字、`.`、`_`、`-`,总长不超过 64 字符。未指定时使用 plan slug 自动生成。
### 创建流程:EnterWorktreeTool
`EnterWorktreeTool`(`packages/builtin-tools/src/tools/EnterWorktreeTool/EnterWorktreeTool.ts`)的执行链路:
```mermaid
graph TD
A["EnterWorktreeTool.call()"] --> B{"是否已在 worktree 中?"}
B -->|是| C["拒绝嵌套"]
B -->|否| D["解析到主仓库根目录"]
D --> E["生成 slug"]
E --> F["createWorktreeForSession()"]
F --> G{"有 WorktreeCreate hook?"}
G -->|是| H["执行 hook 返回路径"]
G -->|否| I["getOrCreateWorktree()"]
I --> J{"目标路径已存在?"}
J -->|是| K["快速恢复: 读取 .git 指针获取 HEAD SHA"]
J -->|否| L["新建 worktree"]
L --> L1["mkdir .claude/worktrees/"]
L1 --> L2["fetch origin/default-branch"]
L2 --> L3["git worktree add"]
L3 --> L4["performPostCreationSetup()"]
K --> M["更新进程状态"]
L4 --> M
H --> M
M --> M1["process.chdir(worktreePath)"]
M1 --> M2["setCwd() / setOriginalCwd()"]
M2 --> M3["saveWorktreeState() 持久化"]
M3 --> M4["clearSystemPromptSections()"]
M4 --> M5["clearMemoryFileCaches()"]
M5 --> N["返回 worktreePath 和 worktreeBranch"]
```
#### Hook 优先的架构
> [!tip]
> `createWorktreeForSession()` 首先检查 `hasWorktreeCreateHook()`——如果用户在 settings.json 中配置了 `WorktreeCreate` hook,系统完全不调用 git,而是执行 hook 命令并将返回的路径作为 worktree 路径。这允许非 git 版本控制系统(如 Pijul、Mercurial)通过 hook 接入。
#### 快速恢复路径
`getOrCreateWorktree()` 有一个关键优化:如果目标路径已存在,直接读取 `.git` 指针文件获取 HEAD SHA(纯文件 I/O,无子进程),跳过整个 `fetch` + `worktree add` 流程。在大仓库中 `fetch` 需要 6-8 秒,这个优化将恢复场景的延迟降到接近 0。
### 退出流程:ExitWorktreeTool
`ExitWorktreeTool`(`packages/builtin-tools/src/tools/ExitWorktreeTool/ExitWorktreeTool.ts`)支持两种退出策略:
#### keep:保留 worktree
```mermaid
graph TD
A["keepWorktree()"] --> B["chdir 回 originalCwd"]
B --> C["清空 currentWorktreeSession"]
C --> D["更新项目配置"]
D --> E["worktree 目录和分支保留在磁盘上"]
```
用户可以通过 `cd <worktreePath>` 继续工作,或稍后手动合并。
#### remove:删除 worktree
有严格的**安全防护**:
```mermaid
graph TD
A["validateInput() 第一道防线"] --> B{"是否在 EnterWorktree 创建的会话中?"}
B -->|否| C["拒绝: 手动创建的 worktree 不会被删除"]
B -->|是| D["countWorktreeChanges()"]
D --> E{"有未提交文件或新提交?"}
E -->|是| F["拒绝, 要求 discard_changes: true"]
E -->|否| G["call() 实际执行"]
D -->|null| H["fail-closed: 拒绝删除"]
G --> G1["重新计数变更"]
G1 --> G2["如果有 tmux session 则 killTmuxSession()"]
G2 --> G3["cleanupWorktree()"]
G3 --> G4["restoreSessionToOriginalCwd()"]
```
#### fail-closed 设计
> [!warning]
> `countWorktreeChanges()` 在以下情况返回 `null`("未知,假设不安全"):
> - `git status` 或 `git rev-list` 退出非零(锁文件、损坏的索引)
> - `originalHeadCommit` 未定义(hook-based worktree 没有设置基线 commit)
>
> 返回 `null` 时,`validateInput` 拒绝删除——宁可让用户手动处理,也不冒险丢失工作。
### 与 Agent 工具的联动
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行。注意 Agent 工具使用**专用的** `createAgentWorktree()`(`src/utils/worktree.ts`),而非用户会话用的 `createWorktreeForSession()`,两者有关键差异:
| 维度 | `createWorktreeForSession`(用户会话) | `createAgentWorktree`(子 Agent) |
|------|---------------------------------------|----------------------------------|
| 调用者 | EnterWorktreeTool | AgentTool |
| Session 管理 | 设置 `currentWorktreeSession` | **不设置** `currentWorktreeSession` |
| 恢复已有 worktree | 直接复用 | 复用并 bump mtime(防止被周期性清理误删) |
子 Agent 结束时的处理由 `cleanupWorktreeIfNeeded()` 自动完成——它不走 `ExitWorktreeTool`(因为 Agent worktree 没有会话状态,`ExitWorktreeTool` 的 `validateInput` 会拒绝):
- **有变更** → 保留 worktree,返回 `worktreePath` 供主 Agent 后续合并
- **无变更** → 自动删除
- **Hook-based** → 始终保留
### Session 状态持久化
`WorktreeSession` 对象通过 `saveCurrentProjectConfig()` 持久化到磁盘,包含:
```typescript
{
originalCwd: string, // 进入 worktree 前的工作目录
worktreePath: string, // worktree 的绝对路径
worktreeName: string, // slug
worktreeBranch?: string, // 分支名(如 worktree/fix-auth)
originalBranch?: string, // 进入前的分支
originalHeadCommit?: string, // 进入前的 HEAD commit(用于变更统计)
sessionId: string, // 创建此 worktree 的会话 ID
tmuxSessionName?: string, // 关联的 tmux session
hookBased?: boolean, // 是否由 hook 创建
creationDurationMs?: number, // 创建耗时(分析用)
usedSparsePaths?: boolean, // 是否使用了 sparse checkout
}
```
> [!tip]
> 这使得 session 恢复(`--resume`)时能正确还原 worktree 上下文——即使进程重启,`getCurrentWorktreeSession()` 从项目配置中读取状态。
### Sparse Checkout 优化
对于大型 monorepo,worktree 支持 `sparsePaths` 配置——只检出特定目录而非整个仓库。这在 210K 文件的仓库中将 worktree 创建时间从数十秒降到几秒。
配置位于 `getInitialSettings().worktree?.sparsePaths`,在 `performPostCreationSetup()` 中应用。
## 关联笔记
- [[sub-agents]]
- [[coordinator-and-swarm]]