Skip to content

ci: reuse Go build objects across pushes of a PR that changes deps - #783

Closed
EricAndrechek wants to merge 6 commits into
ci/remote-make-cifrom
ci/pr-build-cache
Closed

EricAndrechek wants to merge 6 commits into
ci/remote-make-cifrom
ci/pr-build-cache

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

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.

  • 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 (deps: bump the go-deps group across 1 directory with 22 updates #730, build(deps): move to otel 1.47 and hold grpc off 1.84 #742, deps: bump the go-deps group with 12 updates #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

  • 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.
  • 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. 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.
  • 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.
  • prune-pr-build-cache.sh --dry-run against the live cache list selects 6 entries (1058 MiB): copies of main'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 on main) and never touches main or merge-queue scopes.
  • 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: 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.

Related Issues

Closes #781. Refs #535 (two generations on main still leave thin headroom). Part of #740.

Stacked on #766 (base ci/remote-make-ci); contains the whole stack below it.

🤖 Generated with Claude Code

EricAndrechek and others added 5 commits October 8, 2026 15:23
…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
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: fcce3b55-614d-4057-a46e-5d982a6948af
📥 Commits

Reviewing files that changed from the base of the PR and between 9748d6d and 41e3755.

📒 Files selected for processing (5)
  • .github/workflows/README.md
  • .github/workflows/pr-cache-cleanup.yml
  • CHANGELOG.md
  • scripts/ci/prune-pr-build-cache.sh
  • scripts/ci/prune-pr-build-cache.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.

📜 Recent review details
🧰 Additional context used
📚 Code guidelines (2)
AGENTS.md — auto-discovered
CONTRIBUTING.md — auto-discovered
📓 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:

  • .github/workflows/pr-cache-cleanup.yml
  • .github/workflows/README.md
Source excerpt: **Docs prose**: never hard-wrap Markdown — one paragraph is one line.

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Files:

  • CHANGELOG.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)

.github/workflows/README.md

[style] ~80-80: Since ownership is already implied, this phrasing may be redundant.
Context: ... that changes a go.mod/go.sum, into its own scope; other refs restore only | `
/.ca...

(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)

🔇 Additional comments (5)
.github/workflows/README.md (1)

80-80: LGTM!

Also applies to: 136-136

CHANGELOG.md (1)

69-69: LGTM!

scripts/ci/prune-pr-build-cache.sh (1)

6-16: LGTM!

Also applies to: 22-22, 39-39, 57-68, 73-73, 79-81, 91-91

.github/workflows/pr-cache-cleanup.yml (1)

18-20: LGTM!

Also applies to: 65-65

scripts/ci/prune-pr-build-cache.test.sh (1)

6-6: LGTM!

Also applies to: 47-51, 59-62, 71-74, 84-108, 135-135


📝 Summary

Summary by CodeRabbit

  • CI Improvements
    • Pull requests that change Go dependency files can now reuse build caches across updates, helping speed up integration and end-to-end checks.
    • Pull requests without Go dependency changes do not save these caches.
    • Unused pull-request build caches are automatically cleaned up, including entries superseded by newer caches or already available on the main branch, as well as caches for closed pull requests.
  • Documentation
    • Updated CI guidance to explain when pull-request build caches are saved and how they are cleaned up.

Walkthrough

The 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 main, are superseded, or belong to closed pull requests.

Changes

PR-scoped Go build cache

Layer / File(s) Summary
Classify Go dependency changes
scripts/classify-paths.sh, scripts/classify-paths.test.sh, scripts/ci/classify-changes.sh, .github/workflows/ci.yml
The path classifier detects go.mod and go.sum changes. The CI wrapper emits go-deps for pull requests and sets it to false for other events or incomplete classifications.
Enable PR-scoped cache reuse
.github/actions/setup-env/action.yml, .github/workflows/ci.yml, .github/workflows/README.md, AGENTS.md, CHANGELOG.md
The setup action adds the go-cache-pr-save input. The integration app and E2E jobs enable it when go-deps is true. The action checks for an exact-key cache hit, then uses the same-suffix fallback and saves the exact key on a miss.
Prune unused PR cache entries
scripts/ci/prune-pr-build-cache.sh, scripts/ci/prune-pr-build-cache.test.sh, .github/workflows/pr-cache-cleanup.yml, Makefile, docs/src/content/docs/development.md, .github/workflows/README.md
The pruning script selects closed-PR entries, entries duplicated by main, and superseded entries within a cache-key flavor. The cleanup workflow runs it after pull-request CI runs. Tests cover selection and deletion behavior; the Makefile includes the test in verification.

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
Loading

Merge Risk: ⚪ Minimal · up to 41e37

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)
Check name Status Explanation
Linked Issues check Passed Issue #781 asks for measured warm-cache benefit and a 10 GB budget assessment. The PR reports measured savings for dependency-changing PRs, explains why typical PRs are excluded, and documents the bud…
Out of Scope Changes check Passed The changes stay within issue #781. The setup action and CI wiring implement selective PR cache reuse. The classifier, pruning workflow, pruning script, tests, documentation, Make targets, and changel…
Docstring Coverage Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 5 files. (3 skipped: 3 u…
Title check Passed The title clearly summarizes the main change: reusing Go build objects across pushes for dependency-changing pull requests.
Description check Passed The description is directly related to the changeset and explains the classifier, cache-save behavior, pruning workflow, budget, and test plan.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
✨ Simplify code
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@github-actions github-actions Bot added documentation Improvements or additions to documentation github_actions Pull requests that update GitHub Actions code area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release labels Oct 8, 2026
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 639c660e-00a5-4136-a582-0a40592205d9
📥 Commits

Reviewing files that changed from the base of the PR and between ebf0c5e and 9748d6d.

📒 Files selected for processing (13)
  • .github/actions/setup-env/action.yml
  • .github/workflows/README.md
  • .github/workflows/ci.yml
  • .github/workflows/pr-cache-cleanup.yml
  • AGENTS.md
  • CHANGELOG.md
  • Makefile
  • docs/src/content/docs/development.md
  • scripts/ci/classify-changes.sh
  • scripts/ci/prune-pr-build-cache.sh
  • scripts/ci/prune-pr-build-cache.test.sh
  • scripts/classify-paths.sh
  • scripts/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.md
  • AGENTS.md
  • docs/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.md
  • AGENTS.md
  • docs/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.
Context: ... that changes a go.mod/go.sum, into its own scope; other refs restore only | `
/.ca...

(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)

Comment thread scripts/ci/prune-pr-build-cache.sh
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>
@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EricAndrechek
EricAndrechek marked this pull request as ready for review October 8, 2026 22:29
@EricAndrechek

Copy link
Copy Markdown
Member Author

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 main via the -trimpath PR #793, so it no longer waits on #760 or #766.


— Posted by Claude Code on behalf of @EricAndrechek

EricAndrechek added a commit that referenced this pull request Oct 9, 2026
)

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release documentation Improvements or additions to documentation github_actions Pull requests that update GitHub Actions code

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant