工作树并行 —— git worktree + 多 Codex 实例,同时修多个 Issue
简介
在实际开发中,我们经常会遇到这样的场景:
- 有 3 个 Issue 需要修复,每个都需要在当前分支上工作
- 一个紧急 Bug 需要立即修复,但你正在 feature 分支上开发
- 需要同时运行多个 AI Agent 实例,各自处理不同的任务
传统的做法是什么?
- stash 当前工作 → 切到目标分支 → 修复 → commit → 切回来 → stash pop
- 或者干脆等一个任务完成后再开始下一个
这两种方式都有明显的问题:stash 流程繁琐且容易出错,串行处理浪费时间。
git worktree 是更好的解决方案。 它允许你从同一个仓库中创建多个独立的工作目录,每个目录可以处于不同的分支或 commit,互不干扰。
结合 Codex CLI,我们可以在每个 worktree 中运行一个独立的 AI Agent 实例,实现真正的并行开发。
一、git worktree 基础
1.1 什么是 worktree?
Git worktree 让你可以在同一个仓库的不同目录下,同时检出不同的分支。
# 常规 Git:一个仓库 = 一个工作目录
my-repo/
├── .git/
├── src/
└── tests/
# worktree:一个仓库 = 多个工作目录
my-repo/
├── .git/ # 共享的 Git 数据库
├── src/ # 主工作目录(main 分支)
└── tests/
my-repo-fix-bug/ # worktree 1(fix/bug-123 分支)
├── .git # 链接到主仓库
├── src/
└── tests/
my-repo-new-feature/ # worktree 2(feat/new-api 分支)
├── .git # 链接到主仓库
├── src/
└── tests/1.2 创建 worktree
# 基于现有分支创建 worktree
cd /opt/data/my-project
git worktree add ../my-project-fix-bug fix/bug-123
# 基于当前分支创建并新建一个分支
git worktree add -b feat/new-feature ../my-project-feature HEAD
# 基于特定 commit 创建(只读模式)
git worktree add --detach ../my-project-review abc1234
# 指定目录和分支
git worktree add /tmp/my-project-hotfix hotfix/urgent-fix1.3 查看和管理 worktree
# 列出所有 worktree
git worktree list
# 输出示例:
# /opt/data/my-project abc1234 [main]
# /opt/data/my-project-fix-bug def5678 [fix/bug-123]
# /opt/data/my-project-feature ghi9012 [feat/new-feature]
# 查看详细信息
git worktree list --porcelain
# 移除 worktree(先确保已提交)
git worktree remove ../my-project-fix-bug
# 清理已删除的 worktree 记录
git worktree prune1.4 worktree 的核心优势
┌─────────────────────────────────────────────────────────┐
│ git worktree 优势 │
├─────────────────────────────────────────────────────────┤
│ ✅ 共享 .git 数据库,磁盘空间高效 │
│ ✅ 各 worktree 完全独立,切换零开销 │
│ ✅ 不需要 stash/pop,状态始终保存 │
│ ✅ 可以同时运行多个构建/测试进程 │
│ ✅ 每个 worktree 可以运行独立的 AI Agent 实例 │
│ ✅ 紧急任务随时处理,不干扰当前工作 │
└─────────────────────────────────────────────────────────┘二、多 Codex 实例并行
2.1 架构设计
主仓库 (/opt/data/my-project)
├── .git/ ← 共享 Git 数据库
├── src/ ← main 分支
└── Codex CLI 实例 A ← 处理 Issue #101
../my-project-worktree-1
├── .git ← 链接到主仓库
├── src/ ← fix/auth-bug 分支
└── Codex CLI 实例 B ← 处理 Issue #102
../my-project-worktree-2
├── .git ← 链接到主仓库
├── src/ ← fix/api-timeout 分支
└── Codex CLI 实例 C ← 处理 Issue #1032.2 创建 worktree 并启动 Codex
import os
import time
PROJECT_ROOT = "/opt/data/my-project"
# 步骤 1:在主仓库中创建两个 worktree
worktrees = [
{
"name": "worktree-auth",
"branch": "fix/auth-bug-101",
"prompt": "修复 Issue #101:用户认证失败的问题。检查 src/auth.py 中的 token 验证逻辑。"
},
{
"name": "worktree-api",
"branch": "fix/api-timeout-102",
"prompt": "修复 Issue #102:API 请求超时问题。检查 src/api/client.py 中的超时配置。"
}
]
for wt in worktrees:
# 创建 worktree 并创建新分支
cmd = f"cd {PROJECT_ROOT} && git worktree add -b {wt['branch']} ../{wt['name']} HEAD"
result = terminal(command=cmd)
print(f"创建 worktree {wt['name']}: exit_code={result.exit_code}")2.3 在每个 worktree 中启动 Codex 实例
# 步骤 2:在每个 worktree 中启动 Codex CLI 实例
codex_sessions = []
for wt in worktrees:
worktree_path = f"/opt/data/{wt['name']}"
# 使用 background + pty 启动 Codex
session = terminal(
command=f"cd {worktree_path} && codex exec -p \"{wt['prompt']}\"",
background=True,
pty=True, # Codex 需要 PTY
timeout=600,
notify_on_complete=True
)
codex_sessions.append({
"name": wt["name"],
"branch": wt["branch"],
"session_id": session.session_id,
"prompt": wt["prompt"]
})
print(f"Codex 实例已启动: {wt['name']} (Session: {session.session_id})")
print(f"\n共启动 {len(codex_sessions)} 个 Codex 实例")2.4 监控所有实例
# 步骤 3:监控所有 Codex 实例的执行进度
active = list(codex_sessions)
while active:
for item in active[:]:
status = process(action="poll", session_id=item["session_id"])
if status.status != "running":
print(f"\n{'='*60}")
print(f"✅ {item['name']} ({item['branch']}) 已完成")
print(f"{'='*60}")
# 获取最终输出
log = process(action="log", session_id=item["session_id"], limit=50)
print(log)
active.remove(item)
if active:
print(f"\n仍在运行: {', '.join(i['name'] for i in active)}")
time.sleep(15)
print("\n🎉 所有任务完成!")三、同时修复多个 Issue:完整实战
3.1 场景设定
假设我们的项目有以下待修复的 Issue:
| Issue | 标题 | 严重程度 | 需要修改的文件 |
|---|---|---|---|
| #201 | 登录页面 XSS 漏洞 | 🔴 紧急 | src/auth/login.py |
| #202 | 用户头像上传失败 | 🟡 重要 | src/upload/avatar.py |
| #203 | 搜索分页计算错误 | 🟢 普通 | src/search/pagination.py |
3.2 完整自动化流程
import json
import time
PROJECT = "/opt/data/my-project"
ISSUES = [
{
"id": 201,
"title": "登录页面 XSS 漏洞",
"branch": "fix/xss-login-201",
"worktree": "wt-201",
"prompt": """修复 Issue #201:登录页面 XSS 漏洞。
问题:用户名输入框没有对特殊字符进行转义。
文件:src/auth/login.py
要求:
1. 对用户名输入进行 HTML 转义
2. 添加输入验证
3. 编写对应的测试用例"""
},
{
"id": 202,
"title": "用户头像上传失败",
"branch": "fix/avatar-upload-202",
"worktree": "wt-202",
"prompt": """修复 Issue #202:用户头像上传失败。
问题:上传超过 5MB 的头像时返回 500 错误。
文件:src/upload/avatar.py
要求:
1. 添加文件大小校验
2. 返回友好的错误信息
3. 支持图片格式验证"""
},
{
"id": 203,
"title": "搜索分页计算错误",
"branch": "fix/search-pagination-203",
"worktree": "wt-203",
"prompt": """修复 Issue #203:搜索分页计算错误。
问题:最后一页的数据计算不正确。
文件:src/search/pagination.py
要求:
1. 修复 offset 计算公式
2. 处理空结果集的情况
3. 添加边界条件测试"""
}
]
# ========== 阶段 1:创建所有 worktree ==========
print("📦 阶段 1:创建 worktree...")
for issue in ISSUES:
cmd = f"cd {PROJECT} && git worktree add -b {issue['branch']} /opt/data/{issue['worktree']} HEAD"
result = terminal(command=cmd)
if result.exit_code == 0:
print(f" ✅ {issue['worktree']} -> {issue['branch']}")
else:
print(f" ❌ {issue['worktree']} 创建失败: {result.output}")
# ========== 阶段 2:启动所有 Codex 实例 ==========
print("\n🤖 阶段 2:启动 Codex 实例...")
sessions = {}
for issue in ISSUES:
worktree_path = f"/opt/data/{issue['worktree']}"
session = terminal(
command=f"cd {worktree_path} && codex exec -p \"{issue['prompt']}\"",
background=True,
pty=True,
timeout=900,
notify_on_complete=True
)
sessions[issue["id"]] = {
"session_id": session.session_id,
"worktree": issue["worktree"],
"branch": issue["branch"]
}
print(f" 🚀 Issue #{issue['id']} -> Session {session.session_id}")
# ========== 阶段 3:等待并监控 ==========
print("\n⏳ 阶段 3:监控执行...")
completed = []
while len(completed) < len(ISSUES):
for issue_id, info in sessions.items():
if issue_id in [c["id"] for c in completed]:
continue
status = process(action="poll", session_id=info["session_id"])
if status.status != "running":
# 获取变更摘要
worktree_path = f"/opt/data/{info['worktree']}"
diff = terminal(
command=f"cd {worktree_path} && git diff --stat",
timeout=30
)
completed.append({
"id": issue_id,
"status": status.status,
"diff": diff.output
})
print(f"\n ✅ Issue #{issue_id} 完成")
print(f" 变更统计:\n{diff.output}")
# ========== 阶段 4:创建提交 ==========
print("\n💾 阶段 4:创建提交...")
for item in completed:
worktree_path = f"/opt/data/{sessions[item['id']]['worktree']}"
branch = sessions[item["id"]]["branch"]
# 在对应的 worktree 中添加和提交
cmds = [
f"cd {worktree_path} && git add -A",
f'cd {worktree_path} && git commit -m "fix: 修复 Issue #{item[\'id\']}"',
]
for cmd in cmds:
r = terminal(command=cmd, timeout=30)
print(f" {cmd.split('/')[-1][:30]}... exit_code={r.exit_code}")
print("\n🎉 所有 Issue 修复完成!")四、高级技巧
4.1 worktree + 裸仓库模式
对于只读审查场景,可以使用裸仓库作为中央存储:
# 创建裸仓库
git clone --bare /opt/data/my-project /opt/data/my-project.git
# 从裸仓库创建多个 worktree
git clone /opt/data/my-project.git /opt/data/work-1
git clone /opt/data/my-project.git /opt/data/work-24.2 不同分支的 Codex 配置
每个 worktree 可以有不同的 Codex 配置:
# 为不同任务设置不同的 CLAUDE.md
configs = {
"wt-201": """# Issue #201 修复指南
- 这是一个安全修复,请格外谨慎
- 所有输入必须进行转义
- 修改后运行安全测试""",
"wt-202": """# Issue #202 修复指南
- 这是用户体验问题
- 保持向后兼容
- 添加适当的错误处理""",
"wt-203": """# Issue #203 修复指南
- 这是逻辑 bug
- 重点检查边界条件
- 确保分页 API 正确工作"""
}
for wt, config in configs.items():
config_path = f"/opt/data/{wt}/CLAUDE.md"
write_file(path=config_path, content=config)4.3 并行测试
在每个 worktree 中独立运行测试,互不干扰:
# 在所有 worktree 中并行运行测试
test_sessions = {}
for issue in ISSUES:
wt_path = f"/opt/data/{issue['worktree']}"
session = terminal(
command=f"cd {wt_path} && pytest tests/ -v --tb=short",
background=True,
timeout=300,
notify_on_complete=True
)
test_sessions[issue["id"]] = session.session_id
print(f"测试已启动: Issue #{issue['id']}")
# 收集所有测试结果
results = {}
for issue_id, sid in test_sessions.items():
result = process(action="wait", session_id=sid, timeout=300)
results[issue_id] = {
"exit_code": result.exit_code,
"passed": result.exit_code == 0
}
for iid, r in results.items():
status = "✅ 通过" if r["passed"] else "❌ 失败"
print(f"Issue #{iid} 测试: {status}")4.4 资源隔离
每个 Codex 实例是独立的进程,可以设置不同的资源限制:
# 使用 systemd-run 或类似工具限制资源
session = terminal(
command="cd /opt/data/wt-201 && systemd-run --scope -p MemoryMax=2G codex exec -p '...'",
background=True,
pty=True
)4.5 批量清理 worktree
# 完成任务后清理所有 worktree
for issue in ISSUES:
wt_path = f"/opt/data/{issue['worktree']}"
# 确保没有未提交的变更
status = terminal(command=f"cd {wt_path} && git status --porcelain")
if status.output.strip():
print(f"⚠️ {issue['worktree']} 有未提交的变更")
else:
# 从主仓库移除 worktree
remove = terminal(
command=f"cd {PROJECT} && git worktree remove {wt_path}"
)
print(f"🗑️ 已移除 {issue['worktree']}")五、注意事项与最佳实践
5.1 分支冲突管理
# 不要对同一个分支创建多个 worktree
# ❌ 错误:两个 worktree 指向同一分支
git worktree add ../wt-1 fix/same-branch
git worktree add ../wt-2 fix/same-branch # 会报错或行为异常
# ✅ 正确:每个 worktree 使用独立分支
git worktree add -b fix/issue-1 ../wt-1 HEAD
git worktree add -b fix/issue-2 ../wt-2 HEAD5.2 Git 操作同步
# 在主仓库 fetch 后,所有 worktree 自动可见
cd /opt/data/my-project
git fetch origin
# 在任一 worktree 中 push 后,主仓库也可见
cd /opt/data/wt-201
git push origin fix/xss-login-2015.3 共享文件修改
⚠️ 注意: 多个 worktree 共享同一个 .git 数据库。如果你在一个 worktree 中修改了 .git/ 目录下的配置,会影响所有 worktree。
# ❌ 避免直接修改 .git 目录
# ✅ 使用 git config 命令
cd /opt/data/wt-201
git config user.name "Agent-201"
git config user.email "agent-201@example.com"5.4 性能考量
┌─────────────────────────────────────────────────┐
│ worktree 并行性能考量 │
├─────────────────────────────────────────────────┤
│ 磁盘 I/O:多个实例同时读写可能成为瓶颈 │
│ CPU:多个 Codex 实例的 CPU 使用率会累加 │
│ 内存:每个实例有独立的内存占用 │
│ 网络:同时请求外部 API 可能触发限流 │
│ │
│ 建议: │
│ - 3-5 个实例通常不会有明显性能问题 │
│ - 超过 5 个实例时关注磁盘 I/O │
│ - 监控内存使用,避免 OOM │
└─────────────────────────────────────────────────┘总结
本文深入讲解了 git worktree 结合 Codex CLI 的并行开发模式:
- git worktree 基础:创建、查看、管理多个独立工作目录
- 多 Codex 实例:每个 worktree 运行独立的 AI Agent,互不干扰
- 完整实战:从创建 worktree 到启动 Codex 到监控到提交的完整流程
- 高级技巧:不同配置、并行测试、批量清理
- 最佳实践:分支管理、同步操作、性能考量
核心要点:git worktree 让你无需 stash/checkout 就能同时工作在多个分支上,结合 Codex CLI 的并行实例,你可以让 AI 同时修复多个 Issue,大幅提升开发效率。
下篇预告
下一篇:《PR Review 实战 —— 临时克隆 + 安全审查、gh pr checkout》
我们将学习如何使用 GitHub CLI 检出 PR 分支,在安全的隔离环境中让 Codex CLI 进行代码审查,生成详细的审查报告。包括临时克隆策略、安全隔离、自动审查报告生成等内容。敬请期待!