Skip to content

fix: 修复 bare cache 分支永久冻结(fetch 空转)#266

Merged
lishuceo merged 3 commits into
mainfrom
fix/bare-cache-stale-refspec
Jun 23, 2026
Merged

fix: 修复 bare cache 分支永久冻结(fetch 空转)#266
lishuceo merged 3 commits into
mainfrom
fix/bare-cache-stale-refspec

Conversation

@lishuceo

@lishuceo lishuceo commented Jun 22, 2026

Copy link
Copy Markdown
Owner

问题

bare cache 的分支会永久冻结在首次创建的 commit,之后远程的所有 push 都不会反映到缓存,导致从缓存 local-clone 出来的每个工作区都是旧码(直到缓存 30 天过期重建)。

根因

src/workspace/cache.tscloneBareAtomicgit clone --bare,而 --bare 不会创建 remote.origin.fetch refspec。于是 fetchIfStale 里的 git fetch --all 只下载 object、刷新 FETCH_HEAD,永不移动 refs/heads/*

实测铁证(本仓库自己的缓存):

commit
bare cache refs/heads/main b82e5f9 #260 (6-09) 冻结
远程真实 main 313dd19 #264 领先 4 个 commit
313dd19 对象在缓存里 commit fetch 下载了,ref 没动
FETCH_HEAD 刷新时间 当天 fetch 在跑,纯空转

三个缓存仓库(anycode / urhox-bench / tradingagents)实测全部 remote.origin.fetch 缺失、refs/remotes/origin/* 为 0。

fetchIntervalMin(默认 10 分钟)与内存版 lastFetchTime Map 都不是长期过期的原因 —— 它们只是节流这个本就无效的 fetch。

修复

核心修复在 src/workspace/cache.tsfetchIfStale:fetch --allfetch --prune origin +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*。显式 refspec 不依赖仓库 remote.origin.fetch config,既让新建 --bare 缓存持续跟随远程前进,也能原地修复存量旧缓存(无需等 30 天过期重建)。--prune 同步远端已删分支。

cloneBareAtomic 保持 git clone --bare(不用 --mirror):新建缓存时 heads 已是最新,新鲜度由上面的显式 refspec fetch 保证;--mirror 会把 GitHub 通告的全部 refs/pull/*(本仓库 259 个 vs heads 56)拉进缓存且永不 prune,徒增体积,故不采用。

修复后最大陈旧窗口 = REPO_CACHE_FETCH_INTERVAL_MIN(默认 10 分钟);若要更"总是最新"可下调该环境变量。

测试

  • cache.test.ts:断言 clone 用 --mirror 且不再用 --bare;stale fetch 用 --prune origin +refs/heads/*:refs/heads/*;新增冻结分支回归守卫(禁止退回 fetch --all)。
  • src/workspace/__tests__/cache.test.ts 23/23 通过,npm run typecheck 0 错误。
  • 完整回归 npx vitest run:1722 passed,唯一失败 memory/quality.test.ts 与本改动无关(沙箱无 embeddings,origin/main 基线同样失败)。

部署提示

合并后需在部署目录 npm install && npm run build && 重启;建议同时手动清一次 .repo-cache 让存量缓存立即重建(否则等下一次访问时由显式 refspec fetch 自愈)。

🤖 Generated with Claude Code

lishuceo and others added 2 commits June 22, 2026 11:43
git clone --bare 不创建 remote.origin.fetch refspec,导致 fetchIfStale
里的 git fetch --all 只下载对象、刷新 FETCH_HEAD,却不会移动 refs/heads/*。
缓存分支因此冻结在首次 clone 的 commit,之后远程的所有 push 都丢失,
直到缓存 30 天过期重建为止 —— 从该缓存 local-clone 出来的每个工作区都是旧码。

两处修复:
- cloneBareAtomic: clone --bare → clone --mirror(隐含 bare 且持久化
  +refs/*:refs/* refspec,新缓存 fetch 即可正确前进)
- fetchIfStale: fetch --all → fetch --prune origin 显式 +refs/heads/*
  与 +refs/tags/* refspec。显式 refspec 不依赖仓库配置,既保证新缓存正确,
  也能原地修复存量旧 --bare 缓存(无需等 30 天重建)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 断言 clone 使用 --mirror 且不再用裸 --bare
- 断言 stale fetch 使用 --prune origin +refs/heads/*:refs/heads/*
- 新增冻结分支回归守卫:禁止退回 fetch --all(无 refspec 时分支不前进)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 35s —— View job


Code Review — bare cache 分支冻结修复

  • 分析 PR 意图与根因
  • 审查 src/workspace/cache.ts 改动
  • 审查测试覆盖
  • 验证下游消费 (manager.ts local clone)

✅ Approved

根因分析准确,修复方案正确且最小化。

根因判断成立:git clone --bare 确实不写入 remote.origin.fetch refspec,后续 git fetch --all 只下载 object、刷新 FETCH_HEAD,从不移动 refs/heads/* —— 缓存分支冻结。改用 --mirror(隐含 --bare + 持久化 +refs/*:refs/*)新建缓存可正确前进。

自愈逻辑成立:fetchIfStale 改用命令行显式 refspec +refs/heads/*:refs/heads/*,不依赖仓库 config,因此对存量旧 --bare 缓存同样生效,无需等 30 天过期重建。--prune 同步远端已删分支,合理。

下游验证:manager.ts:121-150 从缓存 git clone --branch <sourceBranch>,--mirror 缓存中 refs/heads/* 完整存在,local clone 正常拿到最新码。

测试质量好:三个用例分别覆盖 clone 用 --mirror/不再用 --bare、stale fetch 用显式 refspec、以及禁止退回 --all 的回归守卫。各用例用不同 repo URL 规避了模块级 lastFetchTime Map 的节流,设计细致。

观察(非阻塞,无需修改)

  • 🟡 (置信度 ~60,低于报告阈值,仅供参考) 初次 clone --mirror 会按 +refs/*:refs/* 拉取服务端通告的全部 ref,GitHub 会通告 refs/pull/*,故首次镜像体积可能略大于旧 --bare。后续 fetch 只更新 heads/tags,影响仅限首次 clone,属可接受取舍。
  • --prune + +refs/tags/*:refs/tags/* 对 tag 的删除同步依赖显式 refspec(而非 --prune-tags),当前写法已能 prune 远端删除的 tag,符合预期。

整体:逻辑正确、根因有实测铁证、测试充分、无安全或资源泄漏问题。LGTM 👍

采纳 review 观察:git clone --mirror 会拉取 GitHub 通告的全部 refs/pull/*
(本仓库 259 个 vs heads 56 个),而 fetchIfStale 只更新 heads/tags,
这些 pull ref 会永久滞留、永不 prune,徒增缓存体积。

实际修复是 fetchIfStale 的显式 refspec(本就不依赖仓库 config),
--bare + 显式 refspec 已覆盖新建与存量缓存的所有场景,--mirror 纯属冗余。
故 clone 改回 --bare,显式 refspec fetch 保持不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lishuceo

Copy link
Copy Markdown
Owner Author

Addressed — 采纳 🟡 观察(refs/pull/* 膨胀):已将 clone --mirror 改回 clone --bare。实际修复是 fetchIfStale 的显式 refspec(不依赖仓库 config),--bare + 显式 refspec 已覆盖新建与存量缓存所有场景,--mirror 冗余且会把本仓库 259 个 refs/pull/*(heads 仅 56)全拉进缓存、永不 prune。commit 5364b19

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 27s —— View job


Code Review — 缓存改回 --bare + 显式 refspec (commit 5364b19)

  • 分析新 commit 意图(采纳上轮 🟡 观察)
  • 审查 cache.ts 改动正确性
  • 验证下游 manager.ts local clone
  • 审查测试覆盖

✅ Approved

这次改动正确地采纳了上一轮 review 中关于 refs/pull/* 膨胀的 🟡 观察,且没有牺牲原 PR 的核心修复。判断如下:

真正的修复仍在 fetchIfStale(成立):clone --barerefs/heads/* 初始即为远程最新,后续陈旧更新由命令行显式 refspec +refs/heads/*:refs/heads/* 负责。该 refspec 不依赖 remote.origin.fetch config,因此:

  • 新建 --bare 缓存能持续跟随远程前进;
  • 存量旧 --bare 缓存(无 refspec)也能原地自愈,无需等 30 天过期重建。

回退 --mirror--bare 合理:--mirror 会把 GitHub 通告的 refs/pull/*(本仓库 259 个 vs heads 56 个)全部拉进缓存,而本模块 fetch 只更新 heads/tags,这些 pull ref 永不被 prune、徒增体积。改回 --bare 消除了这一取舍。

bare repo 下的关键正确性点(已确认):git fetch 拒绝更新「当前分支」仅针对非 bare 仓库(refusing to fetch into current branch ... of non-bare repository)。bare 缓存无工作树,因此 +refs/heads/*:refs/heads/* 可安全直写 refs/heads/main(含 HEAD 指向的分支)—— 这正是必须保持 --bare(而非普通 clone)的原因。

下游验证:manager.ts:120-149 从缓存 git clone --branch <sourceBranch>,--bare 缓存 refs/heads/* 完整且经 fetch 持续更新,local clone 正常拿到最新码。

--prune + 显式 tag refspec:--prune 配合命令行 +refs/tags/*:refs/tags/* 即可同步远端删除的 tag(无需 --prune-tags),写法正确。

测试质量好:

  • clone 断言 --barenot.toContain('--mirror') —— 守住本次回退;
  • stale fetch 断言 --prune / origin / +refs/heads/*:refs/heads/*;
  • 专门的回归守卫 not.toContain('--all') —— 防止退回最初导致冻结的写法;
  • 各用例用不同 repo URL 规避模块级 lastFetchTime Map 节流,设计细致。

整体:逻辑正确、根因有实测铁证、回退取舍恰当、测试充分,无安全或资源泄漏问题。LGTM 👍

提醒(与 PR 描述一致,无需改动):合并后部署目录需 npm install && npm run build && 重启;存量缓存会在下次访问时由显式 refspec fetch 自愈,无需手动清理。

@lishuceo lishuceo merged commit bcf0b1b into main Jun 23, 2026
4 checks passed
@lishuceo lishuceo deleted the fix/bare-cache-stale-refspec branch June 23, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant