问题
extensions/workflows/replay-safety.ts 中的 repositoryFingerprint()(截至 efd804d)按如下顺序执行 git 命令,导致最常见场景下最贵的一步被白白计算:
git diff --no-ext-diff --no-textconv --binary HEAD --(第 432 行)—— 捕获完整脏工作区 diff,缓冲上限 32 MiB
git ls-files -s -z(第 440 行)
git ls-files --others --ignored --exclude-standard --directory -z(第 456 行)—— 只要存在任何 ignored 文件就会 throw
任何含 ignored 文件的仓库(也就是所有有 node_modules、构建产物或 .env 的仓库)必然在第 3 步 throw,createReplayIdentity() 返回 undefined,这类仓库的 replay 本来就整体禁用。也就是说第 1–2 步每次都是算了就扔,而且每次 replay-safe 的 workflow agent() 调用都会发生一次(extensions/workflows/index.ts:1670)。最热的路径是:主 agent 正在改代码(工作区脏、diff 大)时 fan-out 只读 explorer 子代理,每个子代理调用都白跑一遍全量 diff。
附带问题:boundedGit() 使用 execFileSync(第 409 行),一次指纹要串行跑 5–6 个 git 子进程,全部同步阻塞在宿主事件循环上,仅有 5 秒 deadline 兜底。
价值
在 Windows 10 / Node v22.23.2 / git 2.45.1 上实测,合成仓库包含 2.29 MiB 脏 diff + node_modules(复现脚本见 Validation):
| 场景 |
现状 |
便宜检查前置 |
纯浪费 |
| 含 ignored 文件的仓库,32 次 replay-eligible 调用 |
共 8568 ms,最差单次阻塞事件循环 274 ms |
共 4114 ms |
52% |
| 干净仓库,单次指纹(replay 真正生效时) |
163.8 ms,每次调用跑两遍 |
不变 |
— |
重排不改变任何输入下的指纹结果:所有失败路径本来就汇入同一个 catch → undefined。现有测试(tests/extensions/workflows/replay-safety.test.ts:455)只断言 undefined、不断言命令顺序,因此完全兼容。
方案
把便宜的终止检查挪到 diff 之前:rev-parse --show-toplevel → ignored 检查(尽早 throw)→ 之后再执行 HEAD、ls-files -s、diff、未跟踪文件哈希。含 ignored 文件的仓库从跑 5–6 个 git 进程降到约 2 个,且完全不跑 diff。成功与失败路径的行为均不变。
可选增强(不阻塞合并):抽出或注入 git 执行器,使回归测试能断言调用顺序;把基准脚本收进 benchmarks/ 目录。
验证
bun test tests/extensions/workflows/replay-safety.test.ts —— 全部指纹/identity 测试原样通过
- 在「脏工作区 + ignored 文件」合成仓库上做改动前后基准对比(脚本随 PR 附上)
非目标
- 不改动 fail-closed 设计:对只读子代理可观察的 ignored 文件依然整体禁用 replay;本 issue 只解决「为注定被丢弃的工作付费」。
worktree-handoff.ts:136 的 --binary diff 是有意为之(捕获 worktree patch,每次 handoff 一次),不在本范围内。
- 将
createReplayIdentity() 改为异步(消除 execFileSync 对事件循环的阻塞)是独立的 follow-up:它涉及导出签名、两个调用点以及 workspace lease 交错语义的变化,应当单独评审。
问题
extensions/workflows/replay-safety.ts中的repositoryFingerprint()(截至efd804d)按如下顺序执行 git 命令,导致最常见场景下最贵的一步被白白计算:git diff --no-ext-diff --no-textconv --binary HEAD --(第 432 行)—— 捕获完整脏工作区 diff,缓冲上限 32 MiBgit ls-files -s -z(第 440 行)git ls-files --others --ignored --exclude-standard --directory -z(第 456 行)—— 只要存在任何 ignored 文件就会 throw任何含 ignored 文件的仓库(也就是所有有
node_modules、构建产物或.env的仓库)必然在第 3 步 throw,createReplayIdentity()返回undefined,这类仓库的 replay 本来就整体禁用。也就是说第 1–2 步每次都是算了就扔,而且每次 replay-safe 的 workflowagent()调用都会发生一次(extensions/workflows/index.ts:1670)。最热的路径是:主 agent 正在改代码(工作区脏、diff 大)时 fan-out 只读 explorer 子代理,每个子代理调用都白跑一遍全量 diff。附带问题:
boundedGit()使用execFileSync(第 409 行),一次指纹要串行跑 5–6 个 git 子进程,全部同步阻塞在宿主事件循环上,仅有 5 秒 deadline 兜底。价值
在 Windows 10 / Node v22.23.2 / git 2.45.1 上实测,合成仓库包含 2.29 MiB 脏 diff +
node_modules(复现脚本见 Validation):重排不改变任何输入下的指纹结果:所有失败路径本来就汇入同一个
catch → undefined。现有测试(tests/extensions/workflows/replay-safety.test.ts:455)只断言undefined、不断言命令顺序,因此完全兼容。方案
把便宜的终止检查挪到 diff 之前:
rev-parse --show-toplevel→ ignored 检查(尽早 throw)→ 之后再执行HEAD、ls-files -s、diff、未跟踪文件哈希。含 ignored 文件的仓库从跑 5–6 个 git 进程降到约 2 个,且完全不跑 diff。成功与失败路径的行为均不变。可选增强(不阻塞合并):抽出或注入 git 执行器,使回归测试能断言调用顺序;把基准脚本收进
benchmarks/目录。验证
bun test tests/extensions/workflows/replay-safety.test.ts—— 全部指纹/identity 测试原样通过非目标
worktree-handoff.ts:136的--binarydiff 是有意为之(捕获 worktree patch,每次 handoff 一次),不在本范围内。createReplayIdentity()改为异步(消除execFileSync对事件循环的阻塞)是独立的 follow-up:它涉及导出签名、两个调用点以及 workspace lease 交错语义的变化,应当单独评审。