Skip to content

chore(ci): lint.yml 的 typecheck 补 build-core 的 turbo 缓存回退 (#5401) - #5542

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5401-merge-group-turbo-cache
Aug 5, 2026
Merged

chore(ci): lint.yml 的 typecheck 补 build-core 的 turbo 缓存回退 (#5401)#5542
os-zhuang merged 1 commit into
mainfrom
claude/issue-5401-merge-group-turbo-cache

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5401

工作流-only 改动,.github/workflows/lint.ymltypecheck job 的 Restore Turbo cache 步加两条 restore-keys净增 21 行、删 0 行,写入方一字未动。

依据:先量再改

本 PR 的全部依据是落在 issue 上的一手测量:#5401 的评论 5194564203(只读调查,无状态干预)。它同时否证了 issue 正文的方向 1,所以本 PR 做的不是方向 1:

  • 正文方向 1 是「给恢复步加 main 前缀 restore-keys」。它早就在文件里:链尾那条 ${{ runner.os }}-turbo-${{ github.job }}-,即 Linux-turbo-typecheck-,比任何「main 前缀」都更宽 —— main 写入的键就是 Linux-turbo-typecheck-main-{sha},裸前缀本就前缀命中它。没有任何 restore-key 能加得更宽。

  • 事故 run 30985524210 / job 92239260750,07:34:18Z 的日志把三条键全列了出来并全部 not found,含最宽那条:

    Cache not found for input keys: Linux-turbo-typecheck-gh-readonly-queue/main/pr-5398-f417863f…-98369a86…,
    Linux-turbo-typecheck-gh-readonly-queue/main/pr-5398-f417863f…-, Linux-turbo-typecheck-
    
  • 而基线 run 30985094934(07:27:59Z)用同一条链命中了 Linux-turbo-typecheck-main-58975525c9bffcc2afa834465a3e4bec4e21cbcb —— 证明 merge_group 的 ref 本就读得到 main 作用域的缓存,不是权限/作用域问题,也不是缺前缀

所以真实缺口是:这条链只覆盖自己的 job 命名空间,而该命名空间存在一个「上一代已消失、当代还没写完」的空窗。

现有 key 的构成 / 加了什么 / 为什么它能命中

typecheck job 的 key 与 main 的写入键:

表达式 merge_group 条目上的实际值
key {os}-turbo-{job}-{ref_name}-{sha} Linux-turbo-typecheck-gh-readonly-queue/main/pr-5398-f417863f…-98369a86…
main 写入(save,未改) 同一表达式,ref_name = main Linux-turbo-typecheck-main-{sha}

加的两条(位于链尾,只读):

${{ runner.os }}-turbo-build-core-${{ github.ref_name }}-
${{ runner.os }}-turbo-build-core-

第二条前缀匹配 ci.yml build-core job 在 main push 上写入的 Linux-turbo-build-core-main-{sha}。两条与 ci.yml 的 Temporal Conformance / Dogfood Verify / Console Pin 三个既有消费 job 的写法逐字节相同(ci.yml:466-467 / 746-747 / 1135-1136),已用脚本断言过集合完全一致,含缩进。

#5401 那次实测反推「若当时有此回退会命中哪个 key」:同一次 main push 的 ci.yml run 30985383135 里,Build CoreSave Turbo cache (main only)07:32:47Z → 07:32:50Z 就 success 了;typecheck 的恢复步在 07:34:18Z 才执行,晚 88 秒。所以那一刻 Linux-turbo-build-core-main-f417863f… 已在池中可读,第 4 条 restore-key 会前缀命中它 —— 同一队列条目的 ci.yml 各 job 也正是在那一分钟里全部命中 -main-f417863f…(Build Core / Test Core (1/3) / Dogfood Regression Gate (1/3)),互为佐证。

而 build-core 跑的是 pnpm build = turbo run build --filter=!@objectstack/docs(整个 workspace 除 docs),是本 job 构建步 turbo run build --filter='./packages/*' --filter='./examples/*^...'超集 —— 与 ci.yml 那三个 job 注释里给出的理由同源。

陈旧 / 异构缓存的正确性

回退到一个更旧由别的 job 写入的缓存都不会产出错误结果:turbo 的缓存是按内容哈希寻址的,每个 task 的复用与否由该 task 的输入哈希决定,而不是由 .turbo/cache 这个目录从哪儿来决定。哈希不匹配的 task 一律重跑,只是少了一次命中;哈希匹配的 task,其输出按定义就是这份输入应当产生的输出。所以「旧而温」严格优于「冷」,「读别的 job 的条目」也只是多一批可能命中的 entry,不引入任何新的正确性面。

写入方保持原样以维持缓存单写者纪律:Save Turbo cache (main only)if: always() && github.event_name == 'push' 与 key 均未改,merge_group 条目仍然只读不写。方向 2(对 cache-save 未完成做短等待)按裁定维持否决 —— 它给每个条目加固定延迟,与提速目标相反。

验证(工作流改动无本地运行时,以下为代偿,未跑的项如实说明)

  • YAML 有效性:node scripts/check-workflow-status-functions.mjs 用 CI 同一个 yaml 解析器 parseDocument 走了全部 22 个 workflow(含本文件):
    check-workflow-status-functions: OK (scanned 22 workflow file(s), 39 job(s), 19 job-level if: expression(s); 9 read needs.*.outputs.*, all naming a status function).,--self-test 34 assertions 通过。另用 Python yaml.safe_load 解析后回读了该步的 key 与 4 条 restore-keys、以及 save 步的 if/key(确认未变)。仓库未依赖 actionlint,容器内也没有,故未跑。
  • 控制字节:check-nul-bytes: OK (scanned 5500 tracked text file(s); …; no raw ASCII control bytes),--self-test 48 assertions 通过;并对改动文件做了越过门禁盲区的自扫(C0 全集 + DEL),零命中。
  • 最小 diff:1 file changed, 21 insertions(+),删除 0 行;两条功能行 + 一段说明注释,写入方与其余 6 个 turbo 恢复步全部未动。
  • 没跑什么:本改动不含任何被测试覆盖的代码,故未跑 pnpm test / pnpm typecheck,也没有可加的单测 —— 这条改动的正确性只能由落地后的 run 日志判定,判据见下。

落地后的可观测判据(「部分温」,不承诺基线的 4 秒)

后续任何紧跟 main 合并入队的 merge_group 条目,其 TypeScript Type Check job 应表现为:

  1. Restore Turbo cache 打印 Cache hit for restore-key: Linux-turbo-build-core-main-{sha}(而不是 0 秒的 Cache not found for input keys: …);
  2. Build workspace packages 明显低于事故那次的 4m54s。

明确不承诺回到基线那 4 秒:build-core 只写 build 任务,不跑 typecheck 任务,所以本 job 的 Type check workspace packages 一步仍只能靠自己的命名空间 —— 命中 build-core 时那一步照旧是冷的。即「暖构建步、不暖 typecheck 步」,这是本改动的设计上限,不是缺陷。

未做的部分(留在 issue 里)

上一代条目为何在 6 分钟内从池中消失,未能证实:GET /repos/…/actions/caches 被调查会话的出网代理拦截。按每代约 12 个 job 键 × 250-300 MB ≈ 3 GB、池上限 10 GB 粗估,新一代写入挤掉上一代是最自然的解释。若成立,则杠杆在池容量 / 条目体积 / 远端缓存(方向 C),按裁定只留 issue 记录不做;sharded test / dogfood 也带同样的「仅自身命名空间」链,但无实测拉力,本 PR 不扩。

工作流-only,无用户可见变更 → 挂 skip-changeset,不带 changeset。


Generated by Claude Code

…5401)

Only main writes the Turbo cache, so a merge_group entry always restores by
PREFIX. This job's chain covered only its own job namespace, which has a
window where that prefix matches nothing at all: the previous main generation
is already gone while the current one is still being written.

Measured 2026-08-05 (run 30985524210, job 92239260750): at 07:34:18Z all
three keys reported "Cache not found" and "Build workspace packages" went
from 4s to 4m54s, while the very same queue entry's ci.yml jobs hit
`-main-f417863f` in that same minute. build-core's entry survives the window
- its main-push save completed 07:32:50Z, 88s earlier - and `pnpm build`
(turbo run build --filter=!@objectstack/docs) is a superset of this job's
build step.

Adds the two build-core fallbacks the Temporal Conformance / Dogfood Verify /
Console Pin jobs in ci.yml already carry, spelled byte-identically. The
writer is untouched: "Save Turbo cache (main only)" keeps its condition and
key, so the single-writer discipline is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 4:46pm

Request Review

@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 5, 2026
@os-zhuang os-zhuang removed the size/s label Aug 5, 2026 — with Claude
@github-actions github-actions Bot added the size/s label Aug 5, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 17:16
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 52caf23 Aug 5, 2026
24 of 25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5401-merge-group-turbo-cache branch August 5, 2026 17:29
os-zhuang pushed a commit that referenced this pull request Aug 5, 2026
本 PR(#5650)自己按新硬步骤执行时,实测结果与原措辞相反:标签 22:03:54 落地,
`opened` run(workflow run 31051251795,创建于 22:03:38)的 Check Changeset job
直到 22:04:35 才启动并做实时标签回读 —— 早于回读 41 秒落地的标签被读到,该 run
直接 **success**,并非预设的红。

于是这一句改为如实描述竞态:首 run 的颜色取决于 runner 启动耗时与 POST 落地
的先后,两个方向都有实例(#5542 红、#5650 绿)。指导语随之从「预期会红」改为
「尽快落标签,把首 run 的颜色当信息而不是自己的判决」。

预设方向被自己的实测证伪时如实记录,而不是让措辞压着实测走。
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…tstack-ai#5650)

* docs(os-dev): PR 收尾清单增补 `skip-changeset` 硬步骤,以回读 PR labels 为闭环 (objectstack-ai#5559)

测试-only / 工作流-only / `.claude/`-only 的 PR 不写 changeset,靠 `skip-changeset`
标签豁免 Check Changeset。此前收尾清单对这一步只字未提,dev 在 PR 正文「声明」了
标签却没有真正挂上,objectstack-ai#5533/objectstack-ai#5538 两例各烧一轮门禁红并由 PM 手工兜底 ——
「声明了 ≠ 执行了」正是 declared ≠ enforced 的 agent 版。

新增条目把它写成与「PR 正文以 Fixes 开头」同级的硬步骤,并按实测校正了三处认知:

- 没有任何自动化会替你挂:`.github/labeler.yml` 九条规则里没有 `skip-changeset`,
  2026-08-05 六例(objectstack-ai#5533/objectstack-ai#5538/objectstack-ai#5542/objectstack-ai#5624/objectstack-ai#5642/objectstack-ai#5645)的该标签全部来自 agent,
  `github-actions[bot]` 一次都没挂过 —— 它只负责 `size/*`、`documentation`、
  `tests`、`ci/cd`。
- 只「加一个标签」,不要整集写入:整集写入会把 bot 刚挂的 `size/*` /
  `documentation` / `tests` 抹掉(objectstack-ai#5533/objectstack-ai#5538/objectstack-ai#5642/objectstack-ai#5645 的 agent `unlabeled`
  事件即此),反向也成立 —— objectstack-ai#5533 的标签 15:46:44 落、15:46:45 就被
  `github-actions[bot]` 的写入抹掉,只活了一秒。
- 闭环是回读而不是 POST:Check Changeset 在 PR 打开后数秒就实时读一次标签
  (objectstack-ai#5580),`opened` run 通常早于标签落地 —— objectstack-ai#5542 的 dev 做对了全套,仍留下
  一条红 `opened` run,之后每一次 run 才被标签豁免。所以要在 bot 平息后回读
  标签、并把读到的列表写进报告。

* docs(os-dev): 首 run 颜色按实测改成双向竞态,不再预设为红 (objectstack-ai#5559)

本 PR(objectstack-ai#5650)自己按新硬步骤执行时,实测结果与原措辞相反:标签 22:03:54 落地,
`opened` run(workflow run 31051251795,创建于 22:03:38)的 Check Changeset job
直到 22:04:35 才启动并做实时标签回读 —— 早于回读 41 秒落地的标签被读到,该 run
直接 **success**,并非预设的红。

于是这一句改为如实描述竞态:首 run 的颜色取决于 runner 启动耗时与 POST 落地
的先后,两个方向都有实例(objectstack-ai#5542 红、objectstack-ai#5650 绿)。指导语随之从「预期会红」改为
「尽快落标签,把首 run 的颜色当信息而不是自己的判决」。

预设方向被自己的实测证伪时如实记录,而不是让措辞压着实测走。

---------

Co-authored-by: os-zhuang <hr@objectstack.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants