Skip to content

perf(workflows): replay 指纹在 ignored 检查注定丢弃结果之前就白跑一次完整 git diff --binary #294

Description

@samsen3

问题

extensions/workflows/replay-safety.ts 中的 repositoryFingerprint()(截至 efd804d)按如下顺序执行 git 命令,导致最常见场景下最贵的一步被白白计算:

  1. git diff --no-ext-diff --no-textconv --binary HEAD --(第 432 行)—— 捕获完整脏工作区 diff,缓冲上限 32 MiB
  2. git ls-files -s -z(第 440 行)
  3. 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)→ 之后再执行 HEADls-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 交错语义的变化,应当单独评审。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions