Repository navigation
ci: reuse Go build objects across pushes of a PR that changes deps - #783
EricAndrechek wants to merge 6 commits into
Conversation
…757) ## Summary - The Makefile exports `GOTOOLCHAIN` derived from `go.mod`'s `go` directive. `auto` means the newer of the local Go and `go.mod`'s, so a newer local Go (or a future runner image) built on a different toolchain than CI and made `make lint-go` panic. `setup-env` exports the same pin to `$GITHUB_ENV` for CI steps that run Go outside `make`, and its comments that called `auto` exact are corrected. The pin is strict, so `go install` in `make tools` also runs on it. - One script, `scripts/ci/go-toolchain.sh`, derives the pin for both the Makefile and setup-env. It prefers go.mod's `toolchain` line and fails with one message unless the result is a full x.y.z version, since `go mod edit -go=1.27` writes a `go` line Go rejects as a toolchain name. A fixture test (`make test-go-toolchain`) is wired into `make verify`. - `development.md` notes the new coupling: raising the `go` directive to a new minor needs a golangci-lint built with that minor or newer (patch bumps do not), bump it with `go get go@1.N.P`, and `make GOTOOLCHAIN=local` overrides the pin. - `pnpm-install` no longer uses `--reporter=silent`, which prints nothing even on failure. It captures pnpm's default output and replays it only on failure, with `--config.confirm-modules-purge=false` so pnpm's purge prompt cannot hang the captured output on a terminal. The capture-and-replay fix suggested in #578 does not work with `silent`, since there is nothing to capture. ## Test plan - [x] `make ci` green locally. - [x] With `GOTOOLCHAIN` unset and local Go 1.27.1, `make lint-go` passes (it panicked before) and a make recipe running `go version` prints `go1.26.6`. - [x] With `minimumReleaseAge` raised temporarily in `pnpm-workspace.yaml`, `make pnpm-install` now prints `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` and exits non-zero; the old command printed nothing. Reverted, not committed. - [x] A successful `make pnpm-install` prints 0 bytes. - [x] `make test-go-toolchain`, `make lint-gha`, `make lint-md`. - [x] Pre-push review: code `ship_it`; docs review `ship_it`. - [ ] First CI run: the "Pin and verify Go toolchain" step prints `go1.26.6 linux/amd64`, and Lint, Unit and Integration stay green. Remaining findings: none. ## Related Issues Closes #580 Closes #578 Part of #740 Stacked on #756 (base `ci/integration-split`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary - The repo's Actions cache sits at the 10 GB cap (10.01 GiB in 34 entries, measured 2026-10-06), so entries are evicted continuously and builds go cold intermittently. A run restores only from its own ref's scope and `main`'s. A PR saved its own ~2.4 GB set only when its key missed `main`'s (it changed go.mod/go.sum/pnpm-lock.yaml, or ran between a `main` bump and `main`'s save); measured 2026-10-07, all 6.1 GiB of PR-scoped entries (18) came from one such PR, against 3.86 GiB (16) on `main`. - `setup-env`: the Go module and build caches, the pnpm store and the Playwright cache now save only on `main` (`actions/cache`) and are restore-only elsewhere (`actions/cache/restore`: PRs, merge-queue refs, branches). `main` restores `gomod-v1` without restore-keys so superseded modules stop accumulating, and the docs job's `pnpm store prune` runs only on `main`, where its result can be saved. golangci and Astro caches are unchanged (about 40 MB and a few KB). - New `pr-cache-cleanup.yml`: on PR close it deletes the caches under the PR's refs (`actions: write` only, no checkout, nothing from the PR executed; fails if either delete fails). It covers what a PR still saves: GitHub's CodeQL dependency cache (about 0.4-0.6 GiB, whenever `main` has no entry for its key) and the small golangci/Astro entries. - `.github/workflows/README.md`, AGENTS.md, `development.md` and the CHANGELOG describe the cache rules, inventory and sizing policy to match. ## Test plan - [x] `make ci` green locally; `make lint-gha` (actionlint) clean. - [x] Pre-push review: code `ship_it`; docs review `ship_it`. - [ ] First PR run: no `Post Run actions/cache` save lines for the Go, pnpm or Playwright caches; the restore steps (`actions/cache/restore`) show a hit or restore-key partial hit for `gomod-v1`, `gobuild-v3-*`, `pnpm`; `gh cache list` shows no new `refs/pull/<n>/merge` Go entries. - [ ] First `main` run after merge: `gomod-v1`, each `gobuild-v3` flavor and `pnpm` restore then save on a key miss, exact hits save nothing; repo usage trends toward about 4 GB. - [ ] While a PR that changes dependencies is open, its restore-only steps keep its own entries warm, so after merge a maintainer may run `gh cache delete --all --ref refs/pull/<n>/merge` (and `/head`) once for any large leftover set. - [ ] This PR's own close is the first live test of the cleanup: the `PR cache cleanup` workflow's `Delete PR caches` job passes and `gh cache list --ref refs/pull/<n>/merge` is empty. It can only be tested after merge (close any PR, then check both refs). Remaining findings: none. ## Related Issues Refs #535 (not closed: two live generations on `main` are about 7.8 GiB of 10, inferred, above the ~70% target; this removes the PR-scoped churn, not all the pressure). Part of #740. Stacked on #757 (base `build/pin-gotoolchain`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Repeated pushes to a pull request reused no Go build objects (#781). Measured on CI runs from 2026-10-06 to 10-08, that costs little on a PR that changes no dependency: the integration suite's app part compiles in 6-8s from main's entry against 2-3s from a same-tree one, and e2e's 4-7s cover build is hidden behind its SDK build, so a cache of its own would buy about 4-5s per push. On a PR that changes go.mod/go.sum it is 35-50s of compile in the integration suite and a 37-49s cover build in e2e on every push, against about 3s and 4s warm. So only the two long poles opt in, with setup-env's new go-cache-pr-save: on a pull request they look up the exact key first (the PR's entry or main's), and only on a miss restore the newest entry of their flavor and save the exact key into the PR's scope. The key is per dependency state, so such a PR saves once per dependency change and a PR whose dependencies match main's saves nothing. pr-cache-cleanup.yml gains a workflow_run job that, after each PR's CI run, runs scripts/ci/prune-pr-build-cache.sh from the default branch with actions: write. It deletes each PR-scoped gobuild-v3 entry that main also holds or that a newer entry of the same flavor supersedes, so no PR code holds the write token, fork PRs are covered, and a PR holds at most about 0.48 GiB of build cache. The README sizes this for four such PRs open at once. Closes #781. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups to the PR-scoped build cache. The save condition was "the key misses", which is wider than "the PR changes its Go dependencies": it also saved whenever main held no entry for the key yet, just after a bump landed on main, after a failed main run, or for a newly opted-in job. The 1058 MiB of prunable entries in the cache today are exactly such copies. The pure classifier now emits go-deps (any go.mod/go.sum in the change set, in any module, the files the cache keys hash), the CI wrapper passes it through for a cleanly classified pull request and sets it false everywhere it fails closed, and the two long poles gate go-cache-pr-save on it. The prune deduplicates the listing before grouping. Paging the caches API can return an entry twice, and two copies of a PR's newest entry made `.[:-1]` delete it. It also lists oldest first, so entries saved mid-listing land on later pages. A fixture test, scripts/ci/prune-pr-build-cache.test.sh, is a new make verify leaf. It covers copies of main's key and version, superseded entries and same-second ties, distinct flavors, other families, main and merge-queue scopes, a duplicated row, and the 404, 403 and failed-listing paths through a stub gh. The README's eviction threshold is now stated with its assumption: with main's superseded generation the size of its live one, the live set fits up to six dependency-changing PRs. It also gives the measured total for the six build flavors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/actions/setup-env/action.yml # .github/workflows/README.md # .github/workflows/pr-cache-cleanup.yml # AGENTS.md # CHANGELOG.md # Makefile # docs/src/content/docs/development.md
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details🧰 Additional context used📚 Code guidelines (2)📓 Path-based instructions (2)Source excerpt: **GitHub Actions supply chain**: Third-party actions are pinned to full commit SHAs with version comments (see `.github/workflows/ci.yml`, `release.yml`).📄 CodeRabbit inference engine (AGENTS.md) Files:
Source excerpt: **Docs prose**: never hard-wrap Markdown — one paragraph is one line.📄 CodeRabbit inference engine (CONTRIBUTING.md) Files:
🪛 LanguageToolCHANGELOG.md[uncategorized] ~69-~69: The official name of this software platform is spelled with a capital “H”. (GITHUB) [uncategorized] ~69-~69: The official name of this software platform is spelled with a capital “H”. (GITHUB) .github/workflows/README.md[style] ~80- (PRP_OWN) [style] ~80-~80: Since ownership is already implied, this phrasing may be redundant. (PRP_OWN) 🔇 Additional comments (5)
📝 SummarySummary by CodeRabbit
WalkthroughThe CI pipeline detects Go dependency changes and uses that result to enable PR-scoped Go build-cache reuse for the integration app and E2E jobs. A cleanup workflow prunes PR cache entries that match ChangesPR-scoped Go build cache
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~30 minutes Change: Feature · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant Changes as CI changes job
participant Classifier as classify-changes.sh
participant Integration as Integration app job
participant E2E as E2E job
participant Setup as setup-env action
participant Cache as Actions cache
Changes->>Classifier: classify changed paths
Classifier-->>Changes: emit go-deps
Changes->>Integration: pass go-deps to app setup
Changes->>E2E: pass go-deps to E2E setup
Integration->>Setup: enable PR cache save when go-deps is true
E2E->>Setup: enable PR cache save when go-deps is true
Setup->>Cache: restore exact key, then fallback on a miss
Setup->>Cache: save exact key after a fallback miss
Merge Risk: ⚪ Minimal · up to This change adds PR-scoped Go build caching for dependency-changing PRs, plus automatic cache pruning. It affects CI only, not production behavior. No blocking issues were found; the first post-merge cleanup run is worth watching. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: ASSERTIVE
- Plan: Advanced
- Run ID:
639c660e-00a5-4136-a582-0a40592205d9
📒 Files selected for processing (13)
.github/actions/setup-env/action.yml.github/workflows/README.md.github/workflows/ci.yml.github/workflows/pr-cache-cleanup.ymlAGENTS.mdCHANGELOG.mdMakefiledocs/src/content/docs/development.mdscripts/ci/classify-changes.shscripts/ci/prune-pr-build-cache.shscripts/ci/prune-pr-build-cache.test.shscripts/classify-paths.shscripts/classify-paths.test.sh
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
📜 Review details
🧰 Additional context used
📚 Code guidelines (5)
.github/workflows/README.md — auto-discovered
.github/copilot-instructions.md — auto-discovered
docs/src/content/docs/development.md — auto-discovered
AGENTS.md — auto-discovered
CONTRIBUTING.md — auto-discovered
📓 Path-based instructions (5)
Source excerpt: **`ci.yml`'s caches are owned end-to-end by `setup-env`** ([.github/actions/setup-env](../actions/setup-env/action.yml)): each cache is a nested `actions/cache` step that restores inline and saves automatically at job end on...
📄 CodeRabbit inference engine (.github/workflows/README.md)
Files:
.github/workflows/ci.yml
See [AGENTS.md](../AGENTS.md) for project conventions, architecture notes, and AI agent instructions.
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
AGENTS.md
Source excerpt: **Integration test needing Docker** → add a subtest under `tests/integration/` (e.g.
📄 CodeRabbit inference engine (docs/src/content/docs/development.md)
Files:
Makefile
Source excerpt: **Never hard-wrap prose.
📄 CodeRabbit inference engine (AGENTS.md)
Files:
CHANGELOG.mdAGENTS.mddocs/src/content/docs/development.md
Source excerpt: **Docs prose**: never hard-wrap Markdown — one paragraph is one line.
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Files:
CHANGELOG.mdAGENTS.mddocs/src/content/docs/development.md
🪛 LanguageTool
CHANGELOG.md
[uncategorized] ~69-~69: The official name of this software platform is spelled with a capital “H”.
Context: ...f CI's two long poles between pushes** (.github/actions/setup-env/action.yml, `.github...
(GITHUB)
[uncategorized] ~69-~69: The official name of this software platform is spelled with a capital “H”.
Context: ....github/actions/setup-env/action.yml, `.github/workflows/{ci.yml,pr-cache-cleanup.yml,...
(GITHUB)
AGENTS.md
[typographical] ~479-~479: Consider using an em dash in dialogues and enumerations.
Context: - CI (ci.yml): a job DAG over the s...
(DASH_RULE)
[uncategorized] ~479-~479: The official name of this software platform is spelled with a capital “H”.
Context: ... check); caches are owned end-to-end by .github/actions/setup-env (nested `actions/cac...
(GITHUB)
.github/workflows/README.md
[uncategorized] ~53-~53: The official name of this software platform is spelled with a capital “H”.
Context: ...re owned end-to-end by setup-env** ([.github/actions/setup-env](../actions/setup-env...
(GITHUB)
[style] ~53-~53: Since ownership is already implied, this phrasing may be redundant.
Context: ...rites nothing. A run restores only from its own ref's scope and from main's, so an en...
(PRP_OWN)
[style] ~53-~53: Since ownership is already implied, this phrasing may be redundant.
Context: ...saves once and its later pushes restore their own entry instead of recompiling the depend...
(PRP_OWN)
[style] ~80-80: Since ownership is already implied, this phrasing may be redundant./.ca...
Context: ... that changes a go.mod/go.sum, into its own scope; other refs restore only | `
(PRP_OWN)
[style] ~80-~80: Since ownership is already implied, this phrasing may be redundant.
Context: ...es' objects. go.mod is in the key for its own reason — the compiler's build ID keys e...
(PRP_OWN)
🪛 zizmor (1.30.1)
.github/workflows/pr-cache-cleanup.yml
[error] 28-28: overly broad permissions (excessive-permissions): actions: write is overly broad at the workflow level
(excessive-permissions)
[error] 20-25: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely
(dangerous-triggers)
[error] 20-25: use of fundamentally insecure workflow trigger (dangerous-triggers): workflow_run is almost always used insecurely
(dangerous-triggers)
[warning] 28-28: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
[warning] 20-25: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting
(concurrency-limits)
[warning] 61-61: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
A CI run still going when its pull request closes can save its build cache in post-job cleanup after pr-cache-cleanup.yml's close job has run. The workflow_run prune that follows kept the newest entry per flavor, so that entry stayed until GitHub's 7-day expiry. The prune now looks up the state of each PR that holds a build entry, one call per PR number read off the cache refs, and deletes every entry under refs/pull/<n>/* of a PR the API reports closed. Open PRs keep the newest-per-flavor rule. A failed lookup never counts as closed: that PR's newest entries stay, and the job fails once the rest is done. The prune job gains pull-requests: read for the lookups. The fixture test gains a closed PR (every entry under both refs goes, other cache families stay), an open PR (newest kept) and a failed lookup (nothing extra deleted, exit 1). All three fail against the previous script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@coderabbitai review |
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Superseded by #794: the same change (diff matches the version CodeRabbit approved here; only the verify-leaf list differs, because the base did) rebuilt on — Posted by Claude Code on behalf of @EricAndrechek |
) Repeated pushes to a PR reused no Go build objects. Measured from CI logs (2026-10-06 to 10-08), that only matters on a PR that changes `go.mod`/`go.sum`, so only that case is cached, and only for the two long-pole jobs. - **Measured, typical PR.** The integration suite's `app` part compiles in 5.8–8.5 s from `main`'s entry, against 2.3–3.2 s from a same-tree one. e2e's 4–7 s cover build is hidden behind its SDK build. A PR-scoped cache would buy about 4–5 s per push, which is inside the `app` part's run-to-run spread, so these PRs still save nothing. - **Measured, dependency-changing PR (#730, #742, #753).** Every push recompiles the dependency delta from `main`'s generation: 35–50 s in the integration suite and a 37–49 s cover build in e2e, against about 3 s and 4 s from the PR's own entry. That is about 30–45 s per push on the critical path. Of 129 PR runs since 2026-09-25, 34 came from 9 such PRs, and 25 of those were second or later pushes. - **Classifier.** `scripts/classify-paths.sh` also emits `go-deps`: any `go.mod`/`go.sum` in the change set, in any module, which are the files the cache keys hash. `classify-changes.sh` passes it through only for a cleanly classified pull request and sets it false everywhere it fails closed. The `changes` job exposes it as an output. - **setup-env.** A new `go-cache-pr-save` input, which the `app` part and e2e set only when `go-deps` is true. So a PR that changes no dependency never saves, even while `main` holds no entry for its key: just after a bump lands on `main`, after a failed `main` run, or for a newly opted-in job. With the input set, the job looks up the exact key first, from the PR's scope or `main`'s, so a PR whose dependency change `main` already has hits `main`'s entry and saves nothing. Only on a miss does it restore the newest entry of its flavor (the PR's own, then `main`'s) and save the exact key into the PR's scope. The key is per dependency state, so a PR saves once per dependency change. Merge-queue refs and branches stay restore-only, and nothing is added to `ci.yml` beyond the two inputs. - **Pruning.** `pr-cache-cleanup.yml` gains a `workflow_run` job that runs after every PR's CI run. It runs the new `scripts/ci/prune-pr-build-cache.sh` from the default branch with `actions: write` and deletes every PR-scoped `gobuild-v3` entry that `main` also holds or that a newer entry of the same flavor supersedes. It deduplicates the paged listing before grouping, since a row returned twice would otherwise delete the PR's newest entry, and a fixture test (`make test-prune-pr-build-cache`, a new `make verify` leaf) pins the selection and the failure handling. No PR code holds the write token. The job needs no PR number, so it covers fork PRs too. A PR holds at most about 0.48 GiB of build cache (double that for the minutes before a prune). - **Budget.** The README sizes this for four such PRs open at once, the most open at once in the last two weeks. The worst case at rest is about 11.7 GiB, about 1.7 GiB over the cap, and eviction by last access takes it from `main`'s superseded generation, which nothing reads (inferred). At two such PRs everything fits. `main`'s live entries stay safe up to six: 3.9 + N × (0.5 CodeQL + 0.48 build) ≤ 10 GiB, assuming the superseded generation is the size of the live one. The six build flavors together are 1.02 GiB on `main`; the issue's "about 1.3 GiB per suffix" was the total for all six of a PR's flavors. ## Test plan This branch's diff matches #783's (same 13 files, +450/−67), which CodeRabbit approved; the only line differences are the verify-leaf list and the `make verify` table row, which differ because the base did. Re-run here: the pre-commit `make verify` (which runs both fixture suites below) and a full `make ci`. Items marked "measured on #783" were not repeated; the CI items are observable once this PR targets `main`. - [x] `make lint-gha` (actionlint, which also checks the new input and the `go-deps` output) and `make lint-sh` clean; `make verify` green in the pre-commit hook, including the new leaf. - [x] `make test-classify-paths`: new `go-deps` cases (root and nested modules, look-alike names, mixed with docs, empty, large input); a mutant that matches only the root `go.mod`/`go.sum` fails it (mutant measured on #783). `classify-changes.sh` against a stub `gh`: `go-deps=true` only for a pull request whose file list holds a `go.sum`; false on push, merge group, dispatch and a failed listing. - [ ] (measured on #783) The setup-env step conditions, evaluated for push to `main`, `pull_request` with and without the input, `merge_group` and `workflow_dispatch`: exactly one restore path runs, and only a PR miss with the input set is save-capable. - [ ] (measured on #783) `prune-pr-build-cache.sh --dry-run` against the live cache list selects 6 entries (1058 MiB): copies of `main`'s keys under #731, #754 and #756. It keeps #753's own entries (one per flavor, not on `main`) and never touches `main` or merge-queue scopes. - [x] `make test-prune-pr-build-cache`: deletes copies of `main`'s key and version and keeps a same key with another version; deletes superseded entries and the older of a same-second pair; keeps `-integration-` apart from `-integration-app-`; ignores other families and `main`/merge-queue scopes; keeps a PR's only entry listed twice. Through a stub `gh`: a 404 reads as already gone, a 403 fails after the rest are attempted, and a failed listing fails. Mutants caught (measured on #783): no early dedup (the pre-fix script), key-only match, merged flavors, no prefix filter, `main` scope included, listing failure ignored, 404 treated as an error, 403 swallowed. - [ ] This PR's own run, which changes no dependency: `Detect changes` logs `go-deps=false`, and every Go job, the `app` part and e2e included, uses the plain restore step, with no `Cache saved` for `gobuild-v3` in any post step. - [ ] After merge, the first `PR cache cleanup` run triggered by a PR's CI (`Prune PR build caches`): passes and deletes the entries the dry run listed, if they are still there. - [ ] The first PR that changes `go.mod`/`go.sum` (the next Dependabot go-deps PR): `Detect changes` logs `go-deps=true`; the first push restores `main`'s generation through restore-keys and saves `gobuild-v3-Linux-go-integration-app-<its hash>` and `-e2e-cov-<its hash>` under `refs/pull/<n>/merge`; the second push exact-hits them, its integration compile is about 3 s and e2e reaches its containers about 20 s after `make` starts; the prune leaves one entry per flavor; the close cleanup deletes them. A fork PR's save is unverified: if GitHub refuses it, the job only logs a warning. - [x] `make ci` passes on this branch, after merging `main` (#793 and #788). ## Related Issues Closes #781. Refs #535 (two generations on `main` still leave thin headroom). Part of #740. Supersedes #783. Now based on `main`: its parent, the `-trimpath` PR #793, has merged. It does not itself use `-trimpath` or `make ci-remote`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Wjv2nu841pxTMdUWSAPmDH --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Repeated pushes to a PR reused no Go build objects. Measured from CI logs (2026-10-06 to 10-08), that only matters on a PR that changes
go.mod/go.sum, so only that case is cached, and only for the two long-pole jobs.apppart compiles in 5.8–8.5 s frommain's entry, against 2.3–3.2 s from a same-tree one. e2e's 4–7 s cover build is hidden behind its SDK build. A PR-scoped cache would buy about 4–5 s per push, which is inside theapppart's run-to-run spread, so these PRs still save nothing.main's generation: 35–50 s in the integration suite and a 37–49 s cover build in e2e, against about 3 s and 4 s from the PR's own entry. That is about 30–45 s per push on the critical path. Of 129 PR runs since 2026-09-25, 34 came from 9 such PRs, and 25 of those were second or later pushes.scripts/classify-paths.shalso emitsgo-deps: anygo.mod/go.sumin the change set, in any module, which are the files the cache keys hash.classify-changes.shpasses it through only for a cleanly classified pull request and sets it false everywhere it fails closed. Thechangesjob exposes it as an output.go-cache-pr-saveinput, which theapppart and e2e set only whengo-depsis true. So a PR that changes no dependency never saves, even whilemainholds no entry for its key: just after a bump lands onmain, after a failedmainrun, or for a newly opted-in job. With the input set, the job looks up the exact key first, from the PR's scope ormain's, so a PR whose dependency changemainalready has hitsmain's entry and saves nothing. Only on a miss does it restore the newest entry of its flavor (the PR's own, thenmain's) and save the exact key into the PR's scope. The key is per dependency state, so a PR saves once per dependency change. Merge-queue refs and branches stay restore-only, and nothing is added toci.ymlbeyond the two inputs.pr-cache-cleanup.ymlgains aworkflow_runjob that runs after every PR's CI run. It runs the newscripts/ci/prune-pr-build-cache.shfrom the default branch withactions: writeand deletes every PR-scopedgobuild-v3entry thatmainalso holds or that a newer entry of the same flavor supersedes. It deduplicates the paged listing before grouping, since a row returned twice would otherwise delete the PR's newest entry, and a fixture test (make test-prune-pr-build-cache, a newmake verifyleaf) pins the selection and the failure handling. No PR code holds the write token. The job needs no PR number, so it covers fork PRs too. A PR holds at most about 0.48 GiB of build cache (double that for the minutes before a prune).main's superseded generation, which nothing reads (inferred). At two such PRs everything fits.main's live entries stay safe up to six: 3.9 + N × (0.5 CodeQL + 0.48 build) ≤ 10 GiB, assuming the superseded generation is the size of the live one. The six build flavors together are 1.02 GiB onmain; the issue's "about 1.3 GiB per suffix" was the total for all six of a PR's flavors.Test plan
make lint-gha(actionlint, which also checks the new input and thego-depsoutput) andmake lint-shclean;make verifygreen in the pre-commit hook, including the new leaf.make test-classify-paths: newgo-depscases (root and nested modules, look-alike names, mixed with docs, empty, large input); a mutant that matches only the rootgo.mod/go.sumfails it.classify-changes.shagainst a stubgh:go-deps=trueonly for a pull request whose file list holds ago.sum; false on push, merge group, dispatch and a failed listing.main,pull_requestwith and without the input,merge_groupandworkflow_dispatch: exactly one restore path runs, and only a PR miss with the input set is save-capable.prune-pr-build-cache.sh --dry-runagainst the live cache list selects 6 entries (1058 MiB): copies ofmain's keys under ci: bump the actions-deps group across 1 directory with 2 updates #731, deps: bump the npm-deps group across 1 directory with 16 updates #754 and ci(integration): split the suite into parallel parts, select by tag #756. It keeps deps: bump the go-deps group with 12 updates #753's own entries (one per flavor, not onmain) and never touchesmainor merge-queue scopes.make test-prune-pr-build-cache: deletes copies ofmain's key and version and keeps a same key with another version; deletes superseded entries and the older of a same-second pair; keeps-integration-apart from-integration-app-; ignores other families andmain/merge-queue scopes; keeps a PR's only entry listed twice. Through a stubgh: a 404 reads as already gone, a 403 fails after the rest are attempted, and a failed listing fails. Mutants caught: no early dedup (the pre-fix script), key-only match, merged flavors, no prefix filter,mainscope included, listing failure ignored, 404 treated as an error, 403 swallowed.Detect changeslogsgo-deps=false, and every Go job, theapppart and e2e included, uses the plain restore step, with noCache savedforgobuild-v3in any post step.PR cache cleanuprun triggered by a PR's CI (Prune PR build caches): passes and deletes the entries the dry run listed, if they are still there.go.mod/go.sum(the next Dependabot go-deps PR):Detect changeslogsgo-deps=true; the first push restoresmain's generation through restore-keys and savesgobuild-v3-Linux-go-integration-app-<its hash>and-e2e-cov-<its hash>underrefs/pull/<n>/merge; the second push exact-hits them, its integration compile is about 3 s and e2e reaches its containers about 20 s aftermakestarts; the prune leaves one entry per flavor; the close cleanup deletes them. A fork PR's save is unverified: if GitHub refuses it, the job only logs a warning.Related Issues
Closes #781. Refs #535 (two generations on
mainstill leave thin headroom). Part of #740.Stacked on #766 (base
ci/remote-make-ci); contains the whole stack below it.🤖 Generated with Claude Code