You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Test Core merge-queue legs restore a cache HIT and still replay only ~1/85 tasks, while main-push legs replay ~50/83 — #5401's queue tax is unpaid for Test Core, and none of the three known mechanisms explain it #10378
Unclaimed finding, no pm:* / domain:* labels — left for triage to rate and route. Produced while verifying #10228's two stated mechanisms (both measured false, see the report comment there); this is the signature that survived the falsification.
The measurement
Same day (2026-08-20), same workflow, same shard, Test Core:
event
run / job
shard
Cached:
wall
merge_group (pr-10291)
job 96486448748
3
1 / 85
19m15s
merge_group (pr-10276)
job 96493089308
2
1 / 79
12m50s
push main (9a1ed7a0)
job 96502967114
3
55 / 83
10m44s
push main (9a1ed7a0)
job 96502967129
1
44 / 79
—
Merge-queue legs — the ones that gate every merge — replay ~1% of their task graph. Main-push legs on the same shards replay 55-66%.
Why the three known mechanisms do NOT explain it
All three were measured and ruled out (method and n in the #10228 report comment):
Not the writer being cancelled (Test Core 的 Turbo 缓存有两条独立的失效机制,#5401 只覆盖了其中零条 —— 写入方 43% 被取消,且缓存键按 shard 分命名空间 #10228 mechanism one). Save Turbo cache (main only) carries if: always(), and GitHub runs always() steps on a cancelled job. Over 151 Test Core legs on main-push runs, 138 saves concluded success; among the 88 legs whose job was cancelled, 82 (93%) still wrote the cache. Individually verifiable: job 96493855475 was cancelled at 5m29s and its Save Turbo cache (main only) step still concluded success.
So: the cache is written, is fresh, and is restored — and the merge-queue leg still cannot use it. Cache age explains none of the spread: Pearson r(own-shard save age, leg duration) = −0.05 (n=105); within-shard −0.18 / +0.07 / −0.24. A leg starting 0.0 min after a completed shard-3 save still took 20.1 min at 1/85 cached.
What that leaves
The restored entry's task hashes do not match the merge-queue tree, near-universally, while they match ~60% for a main-push tree restoring the same namespace. Candidate causes, none verified:
the queue tree is main + one or more stacked PRs, so the batch's combined diff invalidates far more of the graph than a single landed commit does;
something in the hash inputs varies with the ref/event rather than with content (turbo.jsonglobalDependencies is only tsconfig.json + tsup.config.ts, so that is not it on its face);
lockfile / ^build cascade: test is dependsOn: ["^build"], so one invalidated root package takes the whole closure with it.
Closing this needs a per-task hash diff (turbo run test --dry=json on a queue tree vs the main tree whose entry it restored), which is a different investigation from #10228's.
It is paid on every merge-queue entry, i.e. on the critical path of every merge, at ~18 merges/day.
⛔ It is not fixable by adding fallback restore-keys. actions/cache/restore restores exactly one entry — the first restore-keys prefix that matches — and Test Core's ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}- already matches on every run. A build-core key appended below it would be dead configuration, never reached. (That is why the chore(ci): lint.yml 的 typecheck 补 build-core 的 turbo 缓存回退 (#5401) #5542 precedent transfers to lint.yml but not here: typecheck's own namespace could be empty, so its build-core key could actually fire.)
Unclaimed finding, no
pm:*/domain:*labels — left for triage to rate and route. Produced while verifying #10228's two stated mechanisms (both measured false, see the report comment there); this is the signature that survived the falsification.The measurement
Same day (2026-08-20), same workflow, same shard,
Test Core:Cached:merge_group(pr-10291)96486448748merge_group(pr-10276)96493089308pushmain (9a1ed7a0)96502967114pushmain (9a1ed7a0)96502967129Merge-queue legs — the ones that gate every merge — replay ~1% of their task graph. Main-push legs on the same shards replay 55-66%.
Why the three known mechanisms do NOT explain it
All three were measured and ruled out (method and n in the #10228 report comment):
Save Turbo cache (main only)carriesif: always(), and GitHub runsalways()steps on a cancelled job. Over 151 Test Core legs on main-push runs, 138 saves concludedsuccess; among the 88 legs whose job was cancelled, 82 (93%) still wrote the cache. Individually verifiable: job96493855475was cancelled at 5m29s and itsSave Turbo cache (main only)step still concludedsuccess.${{ runner.os }}-turbo-${{ github.job }}-that Test Core 的 Turbo 缓存有两条独立的失效机制,#5401 只覆盖了其中零条 —— 写入方 43% 被取消,且缓存键按 shard 分命名空间 #10228 proposes adding is already present —ci.yml:226for Test Core,ci.yml:905for Dogfood.)0 秒, MISS). Here every leg hits: over 59 sampled legs theRestore Turbo cachestep ran p50 17s (push) / 18s (merge_group), min 3.0s, and 0 of 59 were ≤1s (MISS-shaped). A real multi-hundred-MB entry is downloaded every time.So: the cache is written, is fresh, and is restored — and the merge-queue leg still cannot use it. Cache age explains none of the spread: Pearson r(own-shard save age, leg duration) = −0.05 (n=105); within-shard −0.18 / +0.07 / −0.24. A leg starting 0.0 min after a completed shard-3 save still took 20.1 min at 1/85 cached.
What that leaves
The restored entry's task hashes do not match the merge-queue tree, near-universally, while they match ~60% for a main-push tree restoring the same namespace. Candidate causes, none verified:
turbo.jsonglobalDependenciesis onlytsconfig.json+tsup.config.ts, so that is not it on its face);^buildcascade:testisdependsOn: ["^build"], so one invalidated root package takes the whole closure with it.Closing this needs a per-task hash diff (
turbo run test --dry=jsonon a queue tree vs the main tree whose entry it restored), which is a different investigation from #10228's.Why it is worth a card
lint.yml's typecheck only, explicitly not to the sharded jobs.actions/cache/restorerestores exactly one entry — the firstrestore-keysprefix that matches — and Test Core's${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-already matches on every run. Abuild-corekey appended below it would be dead configuration, never reached. (That is why the chore(ci): lint.yml 的 typecheck 补 build-core 的 turbo 缓存回退 (#5401) #5542 precedent transfers tolint.ymlbut not here: typecheck's own namespace could be empty, so its build-core key could actually fire.)Related
lint.ymltypecheck only.github/workflows/ci.yml(Turbo restore/save steps),turbo.jsonGenerated by Claude Code