The pr-checks.yml Haskell dependency cache never hits on merge-queue runs, so every queued PR pays a full cold cabal build of all dependencies (~22 min) on top of the actual build+test.
Evidence (two consecutive merge-queue runs, both cold)
- pr-81 run — "Restore cached build artifacts" and "Restore cached dependencies" both logged
Cache not found for input keys: pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<hash>, pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0- (and the -dist-<hash> equivalent). Install dependencies: ~22 min (14:48:27 → 15:10:20 UTC), then Build ~7 min, tests ~3 min.
- pr-82 run (immediately after pr-81 merged) — Install dependencies: 15:24:53 → 15:47:33 = ~22.7 min, again cold. Build ~7 min.
Observed cache keys: pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<plan-hash> (deps) and pr-checks-Linux-ghc-9.10.2-dist-<content-hash> (build artifacts).
Diagnosis
Two compounding causes:
-
The push-warm races the merge queue. The intent (warm a base cache on push to the integration branch) can't win during a burst of merges: when PR N merges, the push-to-branch warm run starts (and is itself cold the first time, ~30 min), but PR N+1's merge_group run starts immediately and reaches its restore step long before the warm has saved. So it misses. Every PR in the burst is one step ahead of the warm.
-
merge_group runs can't share caches with each other. Each runs on a distinct gh-readonly-queue/<branch>/pr-<N>-<sha> ref. GitHub Actions scopes cache reads to the current ref + its base branch + the default branch, and a cache saved during a merge_group run is keyed to that ephemeral ref — invisible to the next PR's merge_group run. So queue runs can only restore caches that were saved directly on the integration branch (a push event) or main — never from a sibling queue run.
Net: during any burst of merges, the dependency cache is perpetually cold and each queued PR wastes ~30 min of compute. It would only appear to "work" for isolated merges with idle gaps long enough for a warm to finish.
Suggested fixes (any one helps; combining is best)
- Split the cache and key deps on the cabal plan hash only. Save a
~/.cabal(store) + dist-newstyle dependencies cache on every push to the integration branch, keyed on the plan.json/cabal.project.freeze hash (stable across PRs that don't change deps). Give merge_group runs a restore-keys prefix (pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-) so even a slightly-stale plan restores the bulk of built deps and turns the ~22-min install into a few minutes.
- Proactively warm via a scheduled job (e.g. hourly/on-merge with
concurrency dedupe) so a usable base cache exists before bursts, decoupled from the merge cadence.
- Optionally have
merge_group runs also Save the deps cache under the branch-scoped key as a best-effort, but note the sibling-invisibility above — the branch/push cache is the reliable source.
Workflow: .github/workflows/pr-checks.yml, steps Restore cached dependencies / Install dependencies / Save cached dependencies in the Haskell Build & Test job. Evidence gathered from recent merge-queue runs on the integration branch.
The
pr-checks.ymlHaskell dependency cache never hits on merge-queue runs, so every queued PR pays a full coldcabal buildof all dependencies (~22 min) on top of the actual build+test.Evidence (two consecutive merge-queue runs, both cold)
Cache not found for input keys: pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<hash>, pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-(and the-dist-<hash>equivalent). Install dependencies: ~22 min (14:48:27 → 15:10:20 UTC), then Build ~7 min, tests ~3 min.Observed cache keys:
pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<plan-hash>(deps) andpr-checks-Linux-ghc-9.10.2-dist-<content-hash>(build artifacts).Diagnosis
Two compounding causes:
The push-warm races the merge queue. The intent (warm a base cache on push to the integration branch) can't win during a burst of merges: when PR N merges, the push-to-branch warm run starts (and is itself cold the first time, ~30 min), but PR N+1's
merge_grouprun starts immediately and reaches its restore step long before the warm has saved. So it misses. Every PR in the burst is one step ahead of the warm.merge_groupruns can't share caches with each other. Each runs on a distinctgh-readonly-queue/<branch>/pr-<N>-<sha>ref. GitHub Actions scopes cache reads to the current ref + its base branch + the default branch, and a cache saved during amerge_grouprun is keyed to that ephemeral ref — invisible to the next PR'smerge_grouprun. So queue runs can only restore caches that were saved directly on the integration branch (apushevent) ormain— never from a sibling queue run.Net: during any burst of merges, the dependency cache is perpetually cold and each queued PR wastes ~30 min of compute. It would only appear to "work" for isolated merges with idle gaps long enough for a warm to finish.
Suggested fixes (any one helps; combining is best)
~/.cabal(store) +dist-newstyledependencies cache on everypushto the integration branch, keyed on theplan.json/cabal.project.freezehash (stable across PRs that don't change deps). Givemerge_groupruns a restore-keys prefix (pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-) so even a slightly-stale plan restores the bulk of built deps and turns the ~22-min install into a few minutes.concurrencydedupe) so a usable base cache exists before bursts, decoupled from the merge cadence.merge_groupruns alsoSavethe deps cache under the branch-scoped key as a best-effort, but note the sibling-invisibility above — the branch/pushcache is the reliable source.Workflow:
.github/workflows/pr-checks.yml, steps Restore cached dependencies / Install dependencies / Save cached dependencies in the Haskell Build & Test job. Evidence gathered from recent merge-queue runs on the integration branch.