对你这个场景,最稳、最容易落地、也最不容易互相踩文件的方案,不是把并行性交给 IDE,而是把并行的最小隔离单元定义成 Git worktree:每个缺陷一个 worktree、一个分支、一个测试上下文、一个总结文件;Claude Code 和 Codex 只是在不同 worktree 里工作的“执行器”。这样做的原因很直接:Git 原生支持多个工作树并行 checkout,共享同一仓库历史与对象库,但每个 worktree 有各自的文件、副本、HEAD 与 index;Claude Code 也明确把 worktree 作为并行隔离手段,Codex 的并行线程和工作树能力同样建立在 Git worktree 之上。citeturn2view10turn15view2turn20view0turn28view0
在 Windows + WSL 场景下,如果你的代理、测试、构建都主要跑在 Linux 命令行里,仓库应放在 WSL Linux 文件系统,而不是 /mnt/c/... 挂载盘上;这是 Microsoft 官方推荐的性能路径。需要注意的是,如果你改用 Codex Windows 原生 app 并让 agent 运行在 Windows native 模式,官方又建议项目放在 Windows 文件系统;但你这份方案假设主执行环境是 WSL 中的 CLI,因此“仓库放 WSL Linux FS”仍然是推荐路径。citeturn2view11turn2view13turn28view1
生产可用的关键,不只是“并行跑起来”,而是把流程分成四个稳定环节:隔离创建、单 writer 执行、本地评审闭环、集成合并。其中“单 writer”是必须明确的人为规则:同一个 worktree 里任何时刻只允许一个可写代理会话;第二个代理如果要介入,要么在另一个 worktree 里写,要么在当前 worktree 里以只读 reviewer 身份工作。Git worktree 解决的是“多工作目录并行 checkout”,不是“同目录多代理写入仲裁”,所以还需要很薄的一层锁文件或 launcher 包装脚本来补齐。citeturn15view4turn20view0turn28view0
对历史整理,推荐采用 每 bug 一个 fix/<BUG-ID> 分支,bug 分支内允许本地 rebase;在集成前先统一 rebase 到最新基线,再用一个 integration branch 做合并回归。只要这些 rebase 还没变成公共协作历史,就安全;Git 官方对已公开提交的 rebase 风险给出的警告非常明确。再配合 git rerere,反复出现的同类冲突会被记录并自动复用,大幅降低多缺陷并行回收时的冲突成本。citeturn30view0turn30view4turn30view2
这份报告以下内容都按你给出的约束来设计:WSL 发行版未指定、CI 提供方未指定、团队规模与远端权限未指定。因此,下面的所有脚本都以 本地 Bash + Git + jq + 可选 gh 为基线;测试命令、lint/typecheck 命令通过每个 bug 的配置文件注入,而不是写死在脚本里。
一页式可运行检查单
# 1) WSL 预置
sudo apt-get update
sudo apt-get install -y git jq bubblewrap
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global fetch.prune true
# 2) 在仓库根目录放入工作流文件
mkdir -p bugs bugspecs summaries scripts .claude/hooks .claude/agents .codex .codex/hooks .codex/agents .agents/skills
# 3) 每个缺陷准备说明和命令
# bugs/BUG-101.md
# bugspecs/BUG-101.env
# 4) 建立 worktree
./scripts/create-worktrees.sh BUG-101 BUG-102 BUG-103
# 5) 启动写入代理
./scripts/launch-agent.sh BUG-101 claude
./scripts/launch-agent.sh BUG-102 codex
# 6) 在各自 worktree 中跑检查并写总结
./scripts/run-checks.sh BUG-101
./scripts/run-checks.sh BUG-102
./scripts/aggregate-summaries.sh BUG-101 BUG-102
# 7) 回收前 rebase + 集成
git -C ../<repo>-wt/BUG-101 fetch origin && git -C ../<repo>-wt/BUG-101 rebase origin/main
git -C ../<repo>-wt/BUG-102 fetch origin && git -C ../<repo>-wt/BUG-102 rebase origin/main
git switch -c integration/defects-$(date +%F) origin/main
git merge --no-ff fix/BUG-101
git merge --no-ff fix/BUG-102
# 8) 可选:推送并创建 PR
./scripts/push-and-pr.sh origin/main上面这套最小路径与 Git worktree、Claude 的 worktree 并行模型、Codex 的默认低摩擦本地权限模式、以及本地 /review 审查能力是兼容的。citeturn20view0turn2view6turn2view7turn12view0
如果目标是“多个测试上报缺陷并行处理、彼此不干扰、后面还能容易 rebase/merge”,我建议把三个候选方案分清楚:
| 方案 | 隔离单位 | 优点 | 缺点 | 结论 |
|---|---|---|---|---|
| Git worktree | 每个缺陷一个 checkout | Git 原生支持多个工作树;共享对象库、远端与历史,但各自拥有文件目录、HEAD、index;Claude 明确支持 worktree 并行;Codex 的 worktree/parallel thread 模式也以 Git worktree 为底层。citeturn2view10turn15view2turn20view0turn28view0 | 还需要你自己补“单 writer”纪律;gitignored 本地文件不会自动随手工 worktree 复制。citeturn20view0turn28view0 | 默认首选 |
| 容器或微 VM | 每个缺陷一个容器/沙箱 | 环境隔离最强;Docker Sandboxes 甚至可提供独立文件系统、网络与 Docker daemon。citeturn13search16turn13search7 | 增加镜像、挂载、依赖缓存、端口、IDE 映射复杂度;共享 workspace 仍然是风险面;对你的“低摩擦 adoption”目标偏重。citeturn13search18turn13search0 | 只在依赖强冲突或不可信代码执行时使用 |
| 普通分支 | 逻辑分支 | Git 基本能力;容易理解。citeturn30view1 | 分支本身不提供并行 checkout;要并行仍要多个工作目录,这正是 git worktree 存在的原因。citeturn2view10turn15view2 | 不适合作为并行隔离主方案 |
因此,推荐的生产默认是:
BUG-123 对应分支 fix/BUG-123。如果你想把这个结构视觉化,可以把它理解成下面这条流:
Claude Code 自带 --worktree,能在仓库根下 .claude/worktrees/<name> 自动创建隔离会话;如果你只用 Claude,这很方便。但你现在要把 Claude + Codex 混合编排,所以统一用 Git 手工 worktree 是更干净的公共基线。Claude 的自动 worktree 可以保留为快捷入口,作为可选模式,而不是总架构。citeturn20view0turn19search0
先讲一个结论:把 repo、代理 CLI、测试命令、汇总文件都放在 WSL 里跑。这样你不会再被 Windows 路径、/mnt/c 性能、换行符与 PowerShell 差异牵着走。Microsoft 对 Linux 命令行工作负载明确建议把项目放在 WSL 文件系统;Codex 在 WSL2 上运行时还依赖 Linux sandbox,官方也要求在 Linux/WSL2 安装 bubblewrap。citeturn2view11turn33search2
建议把仓库根目录整理成下面这样:
repo/
├─ AGENTS.md
├─ CLAUDE.md
├─ bugs/
│ ├─ BUG-101.md
│ └─ BUG-102.md
├─ bugspecs/
│ ├─ BUG-101.env
│ └─ BUG-102.env
├─ summaries/
├─ scripts/
├─ .claude/
│ ├─ settings.json
│ ├─ hooks/
│ ├─ agents/
│ └─ skills/
├─ .codex/
│ ├─ config.toml
│ ├─ hooks.json
│ ├─ hooks/
│ └─ agents/
└─ .agents/
└─ skills/AGENTS.md 作为跨代理共享约定,CLAUDE.md 直接 @AGENTS.md 导入即可,这样 Claude 和 Codex 共用一套 repo 规则,而 Claude 还能在其上叠加少量专属规则。Claude 官方明确支持 CLAUDE.md 导入 AGENTS.md;Codex 则会在启动时分层发现 AGENTS.md,最近目录的内容覆盖更上层内容。citeturn21view0turn21view3
实际操作建议按这个顺序跑:
第一步:初始化本机 Git 与运行前依赖
# WSL shell
sudo apt-get update
sudo apt-get install -y git jq bubblewrap
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global fetch.prune true
git config --global pull.ff only这里最关键的是 rerere。Git 官方说明它会记录你第一次手工解决冲突的结果,并在后续遇到相同冲突时自动复用,非常适合“多个 topic branch 反复 rebase/merge”的工作流。citeturn30view2
第二步:为每个缺陷建立两个文件
第一个文件是 bugs/BUG-101.md,放缺陷上下文;第二个文件是 bugspecs/BUG-101.env,放这个 bug 的最小验证命令。因为你的技术栈、测试框架、CI 都是未指定的,所以不要把测试/lint/typecheck 命令写死在统一脚本里。
bugs/BUG-101.md 示例:
# BUG-101
## 来源
测试提交 / 缺陷平台 / 手工记录
## 现象
保存订单后页面显示成功,但后端实际未持久化。
## 复现
1. ...
2. ...
3. ...
## 预期
成功提示只能在持久化成功后出现。
## 约束
- 不要改动订单导出模块
- 优先最小修复
- 必须覆盖回归测试bugspecs/BUG-101.env 示例:
TEST_CMD='pnpm vitest tests/order-persist.spec.ts'
LINT_CMD='pnpm lint'
TYPECHECK_CMD='pnpm tsc -p tsconfig.json --noEmit'
SMOKE_CMD='pnpm test:e2e --grep "order save"'第三步:批量创建 worktree
统一用手工 Git worktree,而不是让单个代理自行创建。这样分支命名、目录命名、后续回收都一致。
./scripts/create-worktrees.sh BUG-101 BUG-102 BUG-103底层会做的事情应该类似:
git fetch origin
git worktree add ../repo-wt/BUG-101 -b fix/BUG-101 origin/main
git worktree add ../repo-wt/BUG-102 -b fix/BUG-102 origin/main
git worktree add ../repo-wt/BUG-103 -b fix/BUG-103 origin/mainGit worktree 文档说明,linked worktree 与主仓库共享仓库元数据,但各自拥有工作目录;git worktree add -b <new-branch> 会在新分支上创建新工作树。Git 也会默认拒绝把已经在别的 worktree checkout 的同一分支再签出一次,这是非常有价值的安全护栏。citeturn15view2turn15view4
第四步:启动写入代理,并强制单 writer 规则
./scripts/launch-agent.sh BUG-101 claude
./scripts/launch-agent.sh BUG-102 codex
./scripts/launch-agent.sh BUG-103 claude这个 launcher 会先进入相应 worktree,再创建 .agent-writer.lock/;如果发现锁存在且 PID 仍在运行,就拒绝第二个写入代理启动。这样一来,你不会在同一个 worktree 里同时跑 Claude 和 Codex 两个可写会话。这个锁不是 Git 提供的,而是你补上的工作流纪律层;它正好补足 Git worktree “隔离 checkout,但不仲裁同目录多写者”的空白。Git 与两家代理文档都支持把并行放到独立 worktree,因此这层锁是合乎它们设计边界的最小增强。citeturn2view10turn20view0turn28view0
第五步:每个 worktree 内按同一修复契约执行
无论写入者是 Claude 还是 Codex,都要求它遵循同一个 closeout 契约:
bugs/<BUG>.md./scripts/run-checks.sh <BUG>summaries/<BUG>.md之所以把这些写进统一契约,而不是只放在 prompt 里,是因为 Claude 的 CLAUDE.md 与 Codex 的 AGENTS.md 都会在每次会话启动时自动加载,是可持续、可共享、可版本化的约束层。citeturn8view0turn21view3
第六步:只读评审,不与 writer 混写
当 BUG-101 用 Claude 修完后,不要立刻再开一个可写 Codex 进去;先跑只读 reviewer:
bug-reviewer subagent,或用内置 /code-review skill。/review,基于本地 branch 与 base branch 的 diff 检查风险。Codex 官方说明这个 reviewer 是专门的审查 agent,不会改动你的 working tree。citeturn22view0turn12view0turn12view1第七步:回收或保留 worktree
完成后,如果你是手工 worktree,就用:
git worktree list
git worktree remove ../repo-wt/BUG-101
git worktree pruneGit 官方说明,git worktree remove 用于移除 linked worktree;如果你手工删了目录,git worktree prune 可以清理残留管理文件。若 worktree 不干净则需 --force。citeturn15view3turn15view1
这里给出的模板遵循一个原则:确定性的事情交给 hooks,判断性的事情交给 reviewer agent。Claude 文档对 hooks 的定位就是“在固定生命周期事件自动执行的 shell/HTTP/LLM/prompt/agent 操作”,Codex 也提供了几乎同样的事件模型;但对生产环境,Claude 官方特别提醒 agent hook 仍属实验性,更适合作为补充,而不是最底层硬策略。citeturn2view2turn9view0turn27view3
Claude Code 项目级设置
.claude/settings.json
{
"worktree": {
"baseRef": "fresh"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/protect_paths.py",
"statusMessage": "Checking protected paths"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/guard_bash.py",
"statusMessage": "Checking shell command policy"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/after_edit.sh",
"statusMessage": "Formatting or lightweight checks"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Return valid JSON only. If the latest assistant output does not clearly include root cause, changed files/modules, validation results, remaining risk, and the path of the summary file to write, return {\"ok\": false, \"reason\": \"补全根因、验证、风险和摘要文件路径,然后再结束。\"}. Otherwise return {\"ok\": true}."
}
]
}
]
}
}这个配置利用了 Claude hooks 的三个能力:PreToolUse 可阻止危险工具调用,PostToolUse 可在 file edit 后执行确定性脚本,Stop prompt hook 可在回合结束时做“是否可收尾”的语义检查。Claude 官方文档直接给出了 PreToolUse、PostToolUse、Stop 与 prompt-based hooks 的模式,而且指出 Stop hook 如果返回未完成,会把 reason 反馈给 Claude 继续工作。citeturn24view2turn27view0turn27view3
.claude/hooks/protect_paths.py
#!/usr/bin/env python3
import json
import os
import sys
from pathlib import Path
PROTECTED = {
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
".env",
".env.local",
"migrations/",
"db/migrations/"
}
def deny(reason: str):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason
}
}))
sys.exit(0)
payload = json.load(sys.stdin)
tool = payload.get("tool_name", "")
tool_input = payload.get("tool_input", {})
if tool in ("Edit", "Write"):
p = tool_input.get("file_path", "")
if any(p == x or p.startswith(x) for x in PROTECTED):
deny(f"Protected path requires manual review: {p}")
sys.exit(0).claude/hooks/guard_bash.py
#!/usr/bin/env python3
import json
import re
import sys
payload = json.load(sys.stdin)
cmd = payload.get("tool_input", {}).get("command", "")
blocked = [
r"\bgit\s+add\s+\.",
r"\bgit\s+commit\b",
r"\bgit\s+push\b",
r"\brm\s+-rf\b"
]
for pattern in blocked:
if re.search(pattern, cmd):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked shell pattern: {pattern}"
}
}))
sys.exit(0)
sys.exit(0).claude/hooks/after_edit.sh
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
# 只做轻量操作,避免每次编辑都跑全量测试
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx|*.json)
if command -v prettier >/dev/null 2>&1; then
prettier --write "$FILE" >/dev/null 2>&1 || true
fi
;;
esacClaude reviewer subagent
.claude/agents/bug-reviewer.md
---
name: bug-reviewer
description: Read-only local reviewer for a completed defect fix. Use proactively after code changes and before final summary.
tools: Read, Grep, Glob, Bash
model: sonnet
maxTurns: 8
---
你是一个严格的本地代码评审者,只做只读检查,不改代码。
评审目标:
- 正确性回归
- 边界条件遗漏
- 缺少测试或测试过窄
- 配置/迁移/锁文件误改
- 并发与状态时序问题
- 与缺陷描述不匹配的“误修”
输出格式:
1. Findings(按 Critical / High / Medium / Low 排序)
2. Evidence(文件路径、符号、命令)
3. Decision(fix-required 或 lgtm-local)
4. Residual risk(即使通过也写一句)Claude subagent 是项目级 markdown 文件,支持 frontmatter 指定 tools、model、permissionMode 等;只给它 Read/Grep/Glob/Bash,就能把 reviewer 固定为只读角色。Claude 也支持把 skill 设成 context: fork 让它在隔离上下文执行。citeturn6view1turn6view4turn31view2
Codex 项目级配置
.codex/config.toml
model = "gpt-5.5"
approval_policy = "on-request"
sandbox_mode = "workspace-write"
review_model = "gpt-5.5"
[agents]
max_threads = 6
max_depth = 1这是 Codex 官方推荐的低摩擦本地模式:workspace-write + on-request。如果你在 WSL2 中跑 Codex,官方要求先装 bubblewrap,否则 Linux sandbox 可靠性会下降。Codex 自定义 agent 的并发上限放在 [agents],默认 max_threads = 6、max_depth = 1 就是一个很好的保守起点。citeturn33search2turn2view7turn32view3
.codex/hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .codex/hooks/protect_bash.py",
"statusMessage": "Checking Bash policy"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 .codex/hooks/stop_gate.py",
"statusMessage": "Running completion gate"
}
]
}
],
"SubagentStart": [
{
"matcher": "reviewer|explorer",
"hooks": [
{
"type": "command",
"command": "python3 .codex/hooks/subagent_context.py",
"statusMessage": "Injecting reviewer context"
}
]
}
]
}
}Codex hooks 支持项目级 .codex/hooks.json 或 .codex/config.toml 内联定义;项目 hooks 只有在项目被 trust 后才会加载。Codex 还要求你通过 /hooks 审阅并 trust 非托管 hook 定义,这对生产环境反而是好事,因为它强制你把 hook 当成“代码”管理。citeturn9view0turn9view1
.codex/hooks/protect_bash.py
#!/usr/bin/env python3
import json
import re
import sys
payload = json.load(sys.stdin)
cmd = payload.get("tool_input", {}).get("command", "")
blocked = [
r"\bgit\s+add\s+\.",
r"\bgit\s+commit\b",
r"\bgit\s+push\b",
r"\brm\s+-rf\b",
]
for pattern in blocked:
if re.search(pattern, cmd):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked command pattern: {pattern}"
}
}))
sys.exit(0)
sys.exit(0).codex/hooks/stop_gate.py
#!/usr/bin/env python3
import json
import subprocess
import sys
from pathlib import Path
payload = json.load(sys.stdin)
# 避免 Stop hook 连续 block 过多次
if payload.get("stop_hook_active"):
sys.exit(0)
def run(cmd: str) -> str:
return subprocess.check_output(cmd, shell=True, text=True).strip()
branch = run("git branch --show-current")
bug_id = branch.split("/")[-1] if "/" in branch else branch
summary = Path("summaries") / f"{bug_id}.md"
checks = Path(".meta") / f"checks-{bug_id}.log"
last_msg = payload.get("last_assistant_message") or ""
missing = []
if not summary.exists():
missing.append(f"缺少总结文件: {summary}")
if not checks.exists():
missing.append(f"缺少检查日志: {checks}")
required_markers = ["根因", "验证", "风险", "摘要文件"]
for marker in required_markers:
if marker not in last_msg:
missing.append(f"最终输出缺少字段: {marker}")
if missing:
print(json.dumps({
"decision": "block",
"reason": "未满足收尾条件:\n- " + "\n- ".join(missing)
}))
sys.exit(0)
sys.exit(0)Codex hooks 文档明确给出了 Stop 事件的输入里包含 last_assistant_message 与 stop_hook_active,并支持在 stdout 返回 {"decision": "block", "reason": "..."} 让 Codex 自动继续下一轮;这非常适合做确定性的本地完工门禁。citeturn11view1turn11view2
Codex reviewer 与 explorer agents
.codex/agents/reviewer.toml
name = "reviewer"
description = "Read-only reviewer for correctness, regression risk, and missing tests on defect-fix branches."
model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = """
Review like an owner.
Prioritize correctness, regression risk, missing tests, config drift, and accidental scope creep.
Lead with concrete findings. If no material issue exists, say LGTM-local and list residual risk in one sentence.
Do not make code changes.
""".codex/agents/explorer.toml
name = "explorer"
description = "Read-only codebase mapper for tracing the real execution path behind a reported defect."
model = "gpt-5.4-mini"
model_reasoning_effort = "medium"
sandbox_mode = "read-only"
developer_instructions = """
Map the execution path for the reported defect.
Find the real entry point, state transitions, side effects, and the narrowest candidate fix area.
Do not propose broad refactors.
Do not write code.
"""Codex 自定义 agent 放在 .codex/agents/,天然支持只读 sandbox_mode = "read-only";这正适合把“探索”和“评审”变成稳定的只读角色,不与 writer 竞争文件写权限。citeturn32view0turn32view4
建议采用下面这个 两层 Git 策略:
fix/<BUG-ID> 分支,在各自 worktree 内独立修。integration/<date> 分支做回归。这样做的好处是,bug 分支的开发与 review 保持局部独立;而真正的“彼此交互影响”只在 integration branch 上发生。Git 官方对 rebase 与 merge 的职责划分也正是如此:rebase 负责把一串提交移到新基底上,merge 负责把另一条历史并入当前分支。citeturn30view0turn30view1
推荐的基线命令如下:
# 在每个 bug worktree 中先对齐最新主线
git fetch origin
git rebase origin/main
# 在主仓库创建集成分支
git switch -c integration/defects-$(date +%F) origin/main
# 顺序合并每个 bug branch
git merge --no-ff fix/BUG-101
git merge --no-ff fix/BUG-102
git merge --no-ff fix/BUG-103这里我推荐在 integration branch 上用 --no-ff,原因不是 Git 要求,而是为了把每个 bug 的边界保留下来,后续回滚、总结与追溯更容易。等你最终推到远端时,是继续保留 merge commit、还是在 GitHub/GitLab 上 squash,由团队策略决定;而你给出的远端/权限策略目前是未指定的,所以本地方案不强依赖某个 forge。
关于 rebase,有一条必须写死进规范里:不要 rebase 已经成为公共协作基线、且别人可能已经基于其工作的提交。Git 官方对这一点的表述非常直接。你这套流程里,只对“每个 bug 的私有修复分支”做 rebase,就在安全边界内。citeturn30view4
遇到冲突时,建议固定按下面这个顺序处理,而不是临场随机发挥:
# 发现冲突后
git diff
# 手工解决冲突
git add <resolved-file-1> <resolved-file-2>
git rebase --continue
# 要放弃这次 rebase
git rebase --abort
# 真要跳过当前冲突 commit
git rebase --skip这些都是 Git rebase 官方文档给出的正统恢复路径。citeturn30view0
如果冲突重复出现,不要硬扛,依赖 rerere。如果冲突已经复杂到肉眼不想看,直接上可视化 git mergetool。Git 官方把 git mergetool 定义为在 merge 冲突后调用外部冲突解决工具的标准接口,并支持 kdiff3、meld、vimdiff、tortoisemerge 等。citeturn30view2turn30view3
git mergetool
git status如果你要把这一套写成团队里的“冲突升级规则”,我建议用下面这组默认门槛:
| 情况 | 处理建议 |
|---|---|
| 同一文件被两个 bug 轻微改动 | 先手工解一次,让 rerere 学住 |
| 同一模块被两个 bug 深改 | 先让两个分支都 rebase 到最新 base,再在 integration branch 合并 |
| 多次 rebase 都在同一组文件打架 | 直接用 git mergetool |
| 合并后出现跨 bug 新回归 | 保留 integration branch,不要立刻碰主分支;先在 integration 上定位并拆分责任 |
| 某个 bug 分支已推送并被别人基于其开发 | 不要再做会改 SHA 的 rebase;改用 merge 或新分支承接 |
最后,再补一条经常被忽略但很重要的建议:如果你发现某个 bug 分支与主线漂移太久,不要等到快发版才同步。Git 官方在 advanced merging 的实践中长期强调的也是“小冲突、早解决”比“最终一次性大冲突”可控得多。citeturn16search5
一个真正有效的本地 review loop,不是“跑完测试就结束”,而是把实现、验证、只读评审、修复回路串成闭环。Codex 官方已经把这件事产品化到了 /review:它可以针对 base branch、未提交变更、单个 commit 或自定义 review 指令运行专门 reviewer,而且 reviewer 不会动 working tree。Claude 这边则可以用内置 /code-review skill,或者像上面那样固定一个只读 bug-reviewer subagent。citeturn12view0turn12view1turn22view0turn31view1
推荐的本地闭环门禁如下:
| 阶段 | 执行者 | 权限 | 通过条件 | 失败动作 |
|---|---|---|---|---|
| 自检 | writer agent | 可写 | run-checks.sh 全部通过;总结文件初稿生成 | 继续修 |
| 只读 code review | reviewer agent | 只读 | 无 Critical/High;无明显缺测;无范围漂移 | Writer 回合修复 |
| 再验证 | writer agent | 可写 | 针对 reviewer 反馈重跑相关命令 | 继续修 |
| 最终收尾 | writer agent | 可写 | 输出根因、改动、验证、风险、摘要路径 | 若缺项则被 Stop gate 继续拉一轮 |
| 人工接受 | 你 | 人工 | diff、日志、summary 可读且可信 | 决定 push / PR |
为了降低你手工来回解释的次数,建议把“评审输出格式”也固定化。例如所有 reviewer 都按下面格式输出:
Decision: fix-required | lgtm-local
Findings:
- [Severity] <summary>
Evidence: <file/symbol/command>
Why it matters: <impact>
Suggested fix: <minimal change>
Coverage:
- Existing tests touched:
- Missing tests:
Residual risk:
- <one sentence>Claude triage prompt
你在 worktree fix/BUG-101 中工作。
先阅读 bugs/BUG-101.md、AGENTS.md、CLAUDE.md。
目标是先定位根因,再给出最小修复方案;除非必要,不要重构无关代码。
在动手修改之前,请先输出:
1. 根因假设
2. 受影响文件/模块
3. 最小修复计划
4. 需要补充的测试
确认计划后再实施,并在结束前写 summaries/BUG-101.md。Codex fix prompt
You are working in worktree fix/BUG-102.
Read bugs/BUG-102.md and AGENTS.md first.
Implement the smallest defensible fix only.
Update or add narrowly scoped regression tests.
Run ./scripts/run-checks.sh BUG-102.
When done, write summaries/BUG-102.md with sections:
- 根因
- 变更
- 验证
- 风险
- 后续建议
Then run a local review against origin/main and address material findings.Codex reviewer prompt
Review this branch against origin/main.
Focus only on correctness, regression risk, concurrency/race issues, config drift, and missing tests.
Ignore style-only nitpicks unless they hide a real bug.
If no material issues remain, say LGTM-local.
If issues exist, rank them by severity and cite exact files/symbols.Claude reviewer delegation prompt
使用 bug-reviewer 子代理审查当前 worktree。
只做只读检查,不修改文件。
重点检查:实现是否真的对应缺陷描述、是否有漏掉的边界条件、是否缺少回归测试、是否误改锁文件/迁移/配置。
输出 Decision、Findings、Coverage、Residual risk。这套 prompt 要真正持久化,不能只放在聊天里。推荐在仓库根加一个 AGENTS.md,让 Codex 直接读;Claude 的 CLAUDE.md 再导入它。
AGENTS.md
# Agent operating contract
## Branch and worktree rules
- One bug -> one worktree -> one branch named fix/<BUG-ID>.
- One worktree may have only one writing agent session at a time.
- Reviewers must be read-only.
## Change scope
- Fix only the reported defect unless a wider change is strictly required.
- Prefer the smallest defensible patch.
- Do not modify lockfiles, migrations, or .env files unless the bug explicitly requires it.
## Validation
- Read bugs/<BUG-ID>.md before changing code.
- Read bugspecs/<BUG-ID>.env before running checks.
- Run ./scripts/run-checks.sh <BUG-ID> before claiming completion.
## Output contract
- Write summaries/<BUG-ID>.md.
- Final answer must include: 根因, 变更, 验证, 风险, 摘要文件.CLAUDE.md
@AGENTS.md
## Claude-specific additions
- Prefer planning before editing when the root cause is unclear.
- Use the bug-reviewer subagent before final closeout.
- If the fix touches billing, auth, or persistence, be extra conservative about scope.Codex 对 AGENTS.md 的分层发现与覆盖规则是官方推荐路径;Claude 官方则明确说它读的是 CLAUDE.md,如果仓库已经有 AGENTS.md,最好的做法就是从 CLAUDE.md 导入它。citeturn21view0turn21view3
你还要求 skill 示例。建议各放一个,作用都叫“bug-closeout”,但各自用原生格式。
Claude skill
.claude/skills/bug-closeout/SKILL.md
---
name: bug-closeout
description: Close out a single defect fix by running checks, preparing the summary, and asking for review.
disable-model-invocation: true
allowed-tools: Bash(./scripts/run-checks.sh *) Read Grep Edit Write
---
Close out bug $ARGUMENTS.
Steps:
1. Read bugs/$ARGUMENTS.md and AGENTS.md.
2. Run ./scripts/run-checks.sh $ARGUMENTS.
3. If checks fail, stop and explain the failure clearly.
4. Write summaries/$ARGUMENTS.md with sections:
- 根因
- 变更
- 验证
- 风险
- 后续建议
5. Ask to invoke bug-reviewer for a read-only review.
6. Do not commit, push, or merge.Claude skills 是 SKILL.md + YAML frontmatter,支持 /skill-name 直接调用,也支持 disable-model-invocation 与 allowed-tools 控制是否允许自动触发和哪些工具免审批。citeturn22view0turn31view0turn31view1turn31view5
Codex skill
.agents/skills/bug-closeout/SKILL.md
---
name: bug-closeout
description: Finalize one bug-fix worktree by running the local validation commands, writing summaries/<BUG-ID>.md, and preparing a review handoff.
---
Inputs:
- BUG_ID
Workflow:
1. Read bugs/BUG_ID.md and AGENTS.md.
2. Read bugspecs/BUG_ID.env to discover project-specific commands.
3. Run ./scripts/run-checks.sh BUG_ID.
4. If checks fail, do not claim completion.
5. Write summaries/BUG_ID.md with:
- 根因
- 变更
- 验证
- 风险
- 后续建议
6. Recommend a local review against origin/main.
Output:
- Short status
- Summary file path
- Whether review is still neededCodex skills 使用 .agents/skills/<name>/SKILL.md,name 与 description 是必需字段;它既可显式调用,也可按 description 进行隐式匹配。citeturn23view0turn23view2turn23view4
下面这几段是你可以直接放进仓库的最小脚本集。它们分别覆盖:创建 worktree、启动 agent、跑验证、汇总总结、可选 push/PR。这样你实际日常只需要维护 bugs/*.md 与 bugspecs/*.env 两类输入文件。
scripts/create-worktrees.sh
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
REPO_NAME="$(basename "$REPO_ROOT")"
WT_ROOT="${WT_ROOT:-$(dirname "$REPO_ROOT")/${REPO_NAME}-wt}"
DEFAULT_BASE="${BASE_REF:-$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || echo origin/main)}"
mkdir -p "$WT_ROOT"
git fetch origin
for BUG_ID in "$@"; do
BRANCH="fix/${BUG_ID}"
WT_DIR="${WT_ROOT}/${BUG_ID}"
if git worktree list --porcelain | grep -Fqx "worktree ${WT_DIR}"; then
echo "[skip] worktree exists: ${WT_DIR}"
continue
fi
git worktree add "${WT_DIR}" -b "${BRANCH}" "${DEFAULT_BASE}"
mkdir -p "${WT_DIR}/.meta" "${WT_DIR}/summaries"
if [[ -f "${REPO_ROOT}/bugs/${BUG_ID}.md" ]]; then
cp -n "${REPO_ROOT}/bugs/${BUG_ID}.md" "${WT_DIR}/.meta/${BUG_ID}.md" || true
fi
echo "[ok] ${BUG_ID} -> ${WT_DIR} (${BRANCH})"
done
git worktree listscripts/launch-agent.sh
#!/usr/bin/env bash
set -euo pipefail
BUG_ID="${1:?BUG_ID required}"
AGENT="${2:?claude|codex}"
REPO_ROOT="$(git rev-parse --show-toplevel)"
REPO_NAME="$(basename "$REPO_ROOT")"
WT_ROOT="${WT_ROOT:-$(dirname "$REPO_ROOT")/${REPO_NAME}-wt}"
WT_DIR="${WT_ROOT}/${BUG_ID}"
if [[ ! -d "$WT_DIR" ]]; then
echo "worktree not found: $WT_DIR" >&2
exit 1
fi
cd "$WT_DIR"
LOCKDIR=".agent-writer.lock"
if [[ -d "$LOCKDIR" ]]; then
if [[ -f "$LOCKDIR/pid" ]] && kill -0 "$(cat "$LOCKDIR/pid")" 2>/dev/null; then
echo "writer already active in $WT_DIR" >&2
cat "$LOCKDIR/owner" 2>/dev/null || true
exit 1
else
rm -rf "$LOCKDIR"
fi
fi
mkdir -p "$LOCKDIR"
echo "$$" > "$LOCKDIR/pid"
{
echo "agent=$AGENT"
echo "bug=$BUG_ID"
echo "pwd=$WT_DIR"
echo "started=$(date -Is)"
} > "$LOCKDIR/owner"
cleanup() { rm -rf "$LOCKDIR"; }
trap cleanup EXIT INT TERM
case "$AGENT" in
claude)
claude
;;
codex)
codex --sandbox workspace-write --ask-for-approval on-request
;;
*)
echo "unknown agent: $AGENT" >&2
exit 2
;;
esacscripts/run-checks.sh
#!/usr/bin/env bash
set -euo pipefail
BUG_ID="${1:?BUG_ID required}"
REPO_ROOT="$(git rev-parse --show-toplevel)"
SPEC="${REPO_ROOT}/bugspecs/${BUG_ID}.env"
if [[ ! -f "$SPEC" ]]; then
echo "missing spec: $SPEC" >&2
echo "expected TEST_CMD / LINT_CMD / TYPECHECK_CMD / SMOKE_CMD in that file" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$SPEC"
set +a
mkdir -p .meta
LOG=".meta/checks-${BUG_ID}.log"
: > "$LOG"
run_step() {
local name="$1"
local cmd="${2:-}"
[[ -z "$cmd" ]] && return 0
{
echo
echo "===== ${name} ====="
echo "\$ ${cmd}"
} | tee -a "$LOG"
if bash -lc "$cmd" 2>&1 | tee -a "$LOG"; then
echo "[pass] ${name}" | tee -a "$LOG"
else
echo "[fail] ${name}" | tee -a "$LOG"
exit 1
fi
}
run_step TEST "${TEST_CMD:-}"
run_step LINT "${LINT_CMD:-}"
run_step TYPECHECK "${TYPECHECK_CMD:-}"
run_step SMOKE "${SMOKE_CMD:-}"
echo "[ok] checks complete -> $LOG"scripts/aggregate-summaries.sh
#!/usr/bin/env bash
set -euo pipefail
mkdir -p summaries
OUT="summaries/_aggregate-$(date +%F-%H%M%S).md"
{
echo "# Aggregate defect summary"
echo
echo "Generated: $(date -Is)"
for BUG_ID in "$@"; do
echo
echo "## ${BUG_ID}"
echo
if [[ -f "summaries/${BUG_ID}.md" ]]; then
cat "summaries/${BUG_ID}.md"
else
echo "_missing summary: summaries/${BUG_ID}.md_"
fi
done
} > "$OUT"
echo "$OUT"scripts/push-and-pr.sh
#!/usr/bin/env bash
set -euo pipefail
BASE="${1:-origin/main}"
BRANCH="$(git branch --show-current)"
git push -u origin "$BRANCH"
if command -v gh >/dev/null 2>&1; then
BODY_FILE=""
BUG_ID="${BRANCH#fix/}"
if [[ -f "summaries/${BUG_ID}.md" ]]; then
BODY_FILE="--body-file summaries/${BUG_ID}.md"
else
BODY_FILE="--fill"
fi
# shellcheck disable=SC2086
gh pr create --base "${BASE#origin/}" --head "$BRANCH" --title "$BRANCH" $BODY_FILE
else
echo "gh not installed; branch pushed only."
figh pr create、gh auth login、gh auth setup-git 都是 GitHub CLI 官方支持路径;如果你的远端不是 GitHub、或者推送/开 PR 权限未指定,就把这个脚本看成可选件。citeturn14search0turn14search1turn14search3
最后,给你一套每个 bug 的完成标准,建议直接贴进 AGENTS.md:
| 检查项 | 完成标准 |
|---|---|
| 缺陷上下文 | bugs/<BUG-ID>.md 已阅读且问题范围未歪楼 |
| 修改范围 | 只动最小必要文件,无无关重构 |
| 测试 | 已更新/新增相关测试 |
| 命令验证 | ./scripts/run-checks.sh <BUG-ID> 通过 |
| 总结文件 | summaries/<BUG-ID>.md 已生成 |
| 评审 | 本地 reviewer 给出 lgtm-local,或所有 material findings 已修复 |
| 风险说明 | 总结中明确写出 residual risk |
| Git 状态 | worktree 内无未解释的脏文件 |
| 集成准备 | 分支已 rebase 到最新 base,或明确决定在 integration branch 解决冲突 |
建议把最终摘要统一成这个模板,方便你以后做日报、PR body、release note 或批量缺陷回收总结:
summaries/BUG-101.md
# BUG-101
## 背景
- 来源:
- 现象:
## 根因
-
## 解决方案
-
## 具体改动
- 文件:
- 关键逻辑:
- 是否新增/更新测试:
## 验证
- TEST:
- LINT:
- TYPECHECK:
- SMOKE:
## 评审结论
- Decision:
- 主要 findings:
- 是否已处理:
## 剩余风险
-
## 后续建议
- 如果你只想记住一句话来执行这整套流程,那就是:
把“并行”交给 worktree,把“写入”限制为单 writer,把“判断”交给只读 reviewer,把“回收”统一放到 integration branch。 这条线同时符合 Git 的工作模型、Claude 的并行 worktree/skills/subagent 机制,以及 Codex 的 sandbox、subagent、/review 与 hook 能力,而且在你当前“Windows 主机 + WSL + 已熟悉 Claude/Codex CLI,但还没引入 hooks/workflows/subagents”的阶段,落地阻力最低。citeturn20view0turn32view2turn12view0turn33search2
加载评论中...