两项任务都有负责人,怎么还是改乱了?
设想一个可以复现的小实验。用户要同时比较两种实现:alpha 将 main.py 改成 print(6 * 7),beta 改成 print(40 + 3)。reader 认领 alpha,reviewer 认领 beta,各有自己的对话历史。
看板没有重复负责人,消息也没串线。但如果两个人的文件工具都指向同一个 main.py,alpha 刚写完,beta 就覆盖了它。reader 再运行时,甚至会看到 43——那并不是自己刚写的版本。
第十七课里的认领锁保护任务的读改写。锁释放以后,工具开始工作;锁不会让同一路径突然变成两份文件。把整个工具运行也塞进锁里可以串行执行,却失去了并行试验的空间。
缺的关系是:这项任务的工具,到底操作哪一个目录?
同名文件,可以属于不同的工作目录
Git worktree 为同一个仓库增加独立工作目录。每个目录有自己的工作文件、HEAD 和 index,Git 对象及部分引用仍然共享。我们给两项任务各准备一条分支和一份目录,让它们从同一个提交开始。这个行为由Git 官方文档定义。
这里最容易忽略的是“从提交开始”。假如主目录 main.py 已被改为 99,但尚未提交,从 HEAD 创建的新目录仍拿到提交中的 0;它不会顺便复制那份临时编辑。开始并行工作前,需要先决定采用哪一个提交作为共同起点。
用户要同时试两种实现:alpha算出42,beta算出43。即使任务owner不同,如果工具还指向同一main.py,后写入的人仍会覆盖前一个。
主目录 main.py = print(0)- 同名文件,两种修改:用户要同时试两种实现:alpha算出42,beta算出43。即使任务owner不同,如果工具还指向同一main.py,后写入的人仍会覆盖前一个。
- 从提交创建 alpha 目录:Harness用固定提交创建codex/alpha分支与独立目录。目录里的main.py是该提交的版本;任务仍等待认领。
- 同一起点,再创建 beta 目录:beta从同一个提交开始,拥有另一份工作文件。两个目录共享Git对象与分支引用,但编辑一份文件不会直接改变另一份。
- 认领之后,把工具绑定到目录:Harness核对最新任务owner与Git归属,预留alpha目录。文件工具绑定这条路径,程序通过subprocess.cwd进入;线程不调用全局os.chdir。
- alpha 先写,再在原目录运行:获得本次写入与运行许可后,alpha修改自己的main.py并得到42。beta仍是起点版本,主目录也未被编辑。
- beta 也修改同名文件:beta把自己那份main.py改为另一种实现,运行得到43。两份工作可以并行保留;认领锁与目录隔离分别解决不同的问题。
- 完成检查,保留成果待审:两个任务可以完成,两个目录仍有改动,收尾应保留。要采用哪一种实现仍需审查与整合;以后合并同一行,Git仍可能报告冲突。
图里两个目录先得到 0,随后分别保留 42 和 43。主目录仍然是 0。这不是给同一个文件加两把锁,而是把两项工作的文件落点分开了。
可以先用一小段 Python 观察内容副本的区别。它只演示独立内容,不实现 Git:
base = {"main.py": "print(0)"}
alpha = dict(base)
beta = dict(base)
alpha["main.py"] = "print(6 * 7)"
beta["main.py"] = "print(40 + 3)"
for name, files in (("main", base), ("alpha", alpha), ("beta", beta)):
print(name, files["main.py"])点击行号设置断点,再点“调试”;也可以直接运行。
输出依次是 main 的 0、alpha 的乘法和 beta 的加法。Git 还会维护各自的提交与索引,以及共享的对象;完整实践会用真实 Git 验证这些目录关系。
创建目录以后,还得把工具接过去
仅仅创建两个 worktree,工具若仍在主目录运行,覆盖问题照旧发生。Harness需要保存任务ID到工作目录的绑定;成员认领之后,再核对最新owner,取出对应路径。
这条路径应该进入实际调用:文件工具相对于它读写,程序通过 subprocess.run(..., cwd=path) 在那里启动。只在提示词里写“请到alpha目录工作”,不能保证工具实现真的使用了它。
两位队友在同一个 Python 进程里运行时,还要小心 os.chdir()。它改变的是整个进程的当前目录,其他线程也会受影响。我们让每个工具闭包持有自己的绝对路径,每次启动程序显式传 cwd,避免由一个线程悄悄改变另一个线程的落点。
绑定同样不授予执行权限。拿到目录只确定“在哪做”;允许写入、允许运行,仍要经过本次宿主策略。目录独立,也不能替代任务owner的核对。
跑通了,为什么还不能直接删?
alpha 输出 42,beta 输出 43,两项固定检查可以完成。但成果还在各自目录里。此时删除目录,会连同未提交改动一起丢掉;先提交了也不代表成果已进入主分支。
因此收尾需要看文件与提交:目录没有本地改动,而且HEAD仍等于创建起点,才可以把它当作未产生新成果的目录清理。有未跟踪文件、被忽略文件或新的提交,都应先保留并审查。任务的 completed、目录的 kept 和分支被合并,是三个不同的事实。
本册实践选择保守规则:有成果就保留,不提供强制丢弃;正在使用的目录先等当前轮次结束。即使任务失败,目录也可能留下有价值的编辑,不能用任务失败代替文件检查。
隔开编辑,会把冲突推到哪里?
两人同时改同一行,现在不会实时覆盖。等你决定把两份成果都合进来,Git仍可能报告冲突。这时两份修改都还在,可以逐项比较,而不是发现某人的文件早已被覆盖。
工作树也不是进程沙箱:两份程序仍可能连接同一个数据库、占用同一个端口,甚至访问目录之外的文件。文件落点、任务负责人、运行许可和外部资源,需要分别设计。本课的固定脚本只验证工作目录隔离,不把它当成权限边界。
实践篇把这个交接展开为三个函数:创建并登记目录、为已认领任务预留路径、检查后保留或移除。两个队友会真实地各改一份main.py;你也可以故意留下一个忽略文件,观察为什么收尾会停下来。
参考方向来自Learn Claude Code s18,产品的worktree用法见Claude Code官方说明。本册的任务绑定表和收尾规则是教学实现;没有把参考中的内部源码描述当作已验证的产品实现。以上来源于2026-10-20核对。