Repository navigation
Conversation
…one (omdsh-dev#960) active-worktree.ts 的三个进程级缓存只存路径、从不校验存在性: - pathTopCache(path → git 顶层)与 linkedWorktreeCache(top → 是否 linked worktree)永不过期; - activeWorktreeRootCache 虽有 15s TTL,但每次过期重扫时,候选路径仍会从上面两个缓存里拿回同一个 已删除的顶层目录,TTL 形同虚设。 结果是:侧栏跟随某个 linked worktree 之后,如果该 worktree 被外部删除(git worktree remove,或直接 在编辑器里删目录),侧栏根目录会永久钉在已删除的路径上 —— 文件树报「文件夹不存在」、git 面板报 「不是 git 仓库」,主仓库明明还健在,却只能重启 DSH 进程才能恢复(omdsh-dev#960)。 改动(每次缓存命中多一次 stat(),相比它要避免的 ~7ms git 进程开销可以忽略;不新增任何 git 探测): - 新增 directoryPresence():**三态**判断,只有 ENOENT / ENOTDIR 才算「已消失」;EACCES / EPERM / EBUSY / EIO / EMFILE(被占用的句柄、杀软扫描、卡住的网络路径)一律按「无法判定」处理 —— 把这类 瞬时错误当成「已消失」会因为一次抖动就把侧栏改到仓库顶层,比它要修的那个陈旧答案更糟; - topOfActivePath():命中缓存且缓存的是路径时校验它,已消失才失效;失效后的**重新探测结果(负答案) deliberately 不写回缓存** —— pathTopCache 没有 TTL,一个由「消失」产生的负答案会把会话永久钉在 仓库顶层,连「worktree 删掉后又建回来」都再也跟不上了; - activeWorktreeRootOf():命中 TTL 缓存时同样校验 root(root === base 时跳过)。 测试(tests/active-worktree.spec.ts,共 11 例): - 原有 omdsh-dev#960 用例:跟随 worktree → 删掉 → 同一会话回落到仓库(TTL 层)+ 新会话不被内层缓存污染; - 复核补的两例:① 目录还在、但 stat 连续报 EPERM/EBUSY/EACCES/EIO(整趟 ask 都失败,模拟被占用的 句柄/杀软/卡死的网络路径)必须继续跟随 worktree,不许改根;② 删掉后**原地重建**的 worktree 必须被 重新跟随(用新会话验证,因为旧会话按文档语义要等 15s TTL 过期)。 两例都按「先破坏实现再跑」验证过:把三态退化成「任何错误=消失」→ ①红;把失效产生的负答案写回缓存 → ②红。 变异实测:还原 src/active-worktree.ts → omdsh-dev#960 用例红在 Received "…\\worktree"(已删除目录); 三态退化 → 瞬时错误用例红;负答案回写 → 重建用例红。 验证(本机 Windows / Node 22): - pnpm exec vitest run tests/active-worktree.spec.ts → 11/11;pnpm typecheck / pnpm lint → 通过 - pnpm test(全量)对照干净 dev:失败集合为子集,无新增失败(详见 PR 描述;两条既有失败在各树上 单独复跑时只剩 plugin-meta —— 那是 Windows tar 输出 CRLF 导致的确定性失败,不是超时) 未做:真机侧栏复现(本机没有装该插件的 DSH profile)。
m4ku1337
force-pushed
the
fix/active-worktree-deleted-root
branch
from
October 16, 2026 21:27
c4cabd6 to
d5853da
Compare
Author
独立复核摘要(两个子代理各自 clone + 实跑)—— 盲复现 PASS,对抗性复核 FAIL:它在本版第一稿里抓到了两个真回归,已在 d5853da 修正
① 盲复现 + 端到端核对 — PASS(针对 c4cabd6)
② 对抗性复核 — FAIL(针对 c4cabd6) 它用注入 stat 失败的方式证明第一稿把 #960 换成了两个同类但更窄的 bug,其中一条比 dev 更糟:
两条都是真的 —— 我按它的建议改了:
它另外的建议(模块头部注释补一句"每次命中多一次 stat 的文件系统成本")也已采纳。 本版验证: 仍未做:真机侧栏复现(本机没有装该插件的 DSH profile); 已知残留(复核者记录,本 PR 不动): |
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #960
问题
侧栏跟随一个 linked worktree 之后,若该 worktree 被外部删除(
git worktree remove,或直接在编辑器/资源管理器里删目录),侧栏根目录会永久钉在已删除的路径上:文件树报"文件夹不存在"、git 面板的所有命令都在不存在的 cwd 里执行报"不是 git 仓库",主仓库明明完好,却只能重启 DSH 进程才能恢复。根因与 #960 一致,是三个进程级缓存只存路径、从不校验存在性:
外层 TTL 到期后会重新扫描,但候选路径仍从
pathTopCache里拿回同一个已删除的顶层目录,linkedWorktreeCache又照样回答"这是 linked worktree",于是 TTL 形同虚设 —— 这正是"永远不会自行恢复"的机制。改动(每次缓存命中多一次
stat())isLiveDirectory();topOfActivePath():命中缓存且缓存的是路径时先校验它仍是目录;已消失 → 删除该条目并重新探测(对不存在的目录,探测返回undefined,候选自然被丢弃)。缓存undefined("不在任何仓库里")的负答案不受影响,非仓库会话依旧免探测 —— 那是这层缓存存在的理由;activeWorktreeRootOf():命中 TTL 缓存时同样校验root;root === base时跳过(这种情况没有更好的回退对象)。一次
stat()相比这层缓存要避免的 ~7msgit rev-parse进程开销可以忽略(#960 里也算过这笔账)。测试(
tests/active-worktree.spec.ts)新增一条真实仓库 + 真实 linked worktree 的用例:会话跟随 worktree → 删除该目录 → ① 同一会话再次询问必须回落到仓库顶层(覆盖 TTL 缓存层);② 新会话询问也不允许把它从内层缓存里复活(覆盖
pathTopCache)。放在文件末尾,以保持既有 LRU 用例对进程级缓存的假设(它就是靠"第 257 个会话把最老的挤出去"来观测上界的)。把
src/active-worktree.ts还原成修复前,这条用例立即红在:即 #960 里"钉死在已删除目录"的现象本身。
文档
CHANGELOG.md/CHANGELOG_EN.md各加一条### Unreleased条目(AGENTS §6 把用户可见改动导向 CHANGELOG)。验证(本机 Windows / Node 22)
pnpm exec vitest run tests/active-worktree.spec.tssrc/active-worktree.tspnpm typecheck/pnpm lintpnpm test(全量)关于全量对照的诚实说明:本机第一次跑全量时(当时另有并行负载)出现 7 条失败,其中 5 条(
git-remote-actions×2、git-graph×2、git×1)在单独复跑时全部通过(那 5 个文件 53/54 通过,唯一失败是plugin-meta),整轮耗时 128s vs 平时 62s,属负载导致的 30s 超时。另两条与本次无关且在干净dev上同样失败:plugin-meta的 packs the icon and locales into the tarball(Windowstar输出 CRLF、用例按换行切分 —— 确定性失败,不是超时),以及git-worktree的 rename-heavy 用例(负载下超时)。未做:真机侧栏复现(本机没有装该插件的 DSH profile);
pnpm test:mount。AI 辅助说明:定位、补丁与验证由 AI 助手按用户指令完成,输出都是本机原始结果;独立子代理的盲复现与对抗性复核结论随后贴在评论里。