chore(ci): cache Go and pinned tools, single release build #67

Merged
deadstyle merged 9 commits from chore/64-ci-pipeline into develop 2026-10-08 22:09:46 -05:00
Owner

Refs #64

CI with a warm cache now takes 37 s instead of 109 s. Release builds the four platform archives once instead of twice, and its installer tests run against the exact archives it publishes.

What changed

  • Re-saving the cache on every run (45 s each time). The cache server matches keys case-insensitively, but setup-go compares them case-sensitively, so it never saw its own hit. A shared .forgejo/actions/setup action now restores two lowercase-keyed caches: Go plus the pinned linters, and the module/build caches. Go is no longer downloaded and the linters aren't rebuilt on every run.
  • Cache trust. Only pushes to develop/main save caches. PR and dispatched runs restore but never write. Before, every run saved. A PR that edits the workflow itself could still write a cache, unless the cache server scopes caches by branch (ADR 0015). The ShellCheck download is now checked against a pinned SHA-256, and the tools cache key includes a hash of the setup action.
  • Superseded PR runs are cancelled. Concurrency groups are per PR. Pushes to develop/main and dispatched runs each get their own group, so they never cancel.
  • Release: make release VERSION=<tag> runs first, then make check reuses those archives (TEST_INSTALL_REUSE_DIST=1 TEST_INSTALL_VERSION=<tag>). There is a new workflow_dispatch dry run that never publishes. The main-ancestry gate and the draft-until-all-assets-uploaded publish step are unchanged.
  • Go 1.27.2 and golangci-lint v2.14.0. govulncheck began failing on 1.27.1, with five net/http and net/textproto advisories, so every run was red. v2.13.2 can't read 1.27.2's export data.
  • Renovate's tool-version patterns now also match .forgejo/actions/. ADR 0015 records the design.

Measurements (same runner, from job log timestamps)

Before After
Warm run, total 109 s (run 703) 37 s (run 747)
Setup before checks 31 s 21 s
Cache save after checks 45 s 0.5 s
Cold run (no cache, saves both caches) ~151 s (run 700) 161 s (run 745)

Check time depends on what changed (run 703's checks took 27 s and run 747's 12 s, because run 747 re-ran an already-tested tree), so the like-for-like comparison is the setup and save rows. Queue time is 0 s in the API for every run, so these figures are execution time. Parallel jobs were rejected because each one would pay ~20 s of setup to save at most ~12 s. The GitHub sync job takes ~6 s every 10 minutes and was left alone.

Verification

  • Local make check with Go 1.27.2 and golangci-lint v2.14.0: 0 issues., No vulnerabilities found., and all 9 installer cases PASS.
  • Injected failure: commit 9fc42ad added a mis-formatted file. Run 749 failed at fmt-check, and the commit status was failure. The commit was then reverted.
  • Cancellation: pushing 9fc42ad cancelled run 748. Its commit shows failure, never success.
  • Release dry run (workflow_dispatch, v0.0.0-dryrun): runs 751 and 752 passed. They built four archives stamped with that version, all 9 installer cases passed against them, and the tag gate and Publish were skipped. The releases API lists no releases.
  • Tag gate checked locally: git merge-base --is-ancestor gives rc=1 for a branch commit and rc=0 for a commit on main.
  • Final head 48db339: ci / check (pull_request) succeeded.

Review

Three fresh-context reviews were run.

  • Spec: PASS. Added queue time to the ADR. The tools key now covers the install recipe.
  • Comments: one Renovate description was over the cap. Fixed, and two comments tightened.
  • Standards: fixed the leak of the release version into make check (renamed RELEASE_VERSION) and the overclaimed cache-trust wording. The following were dismissed with run evidence:
    • The inputs.version concern: run 751 stamped v0.0.0-dryrun.
    • Composite env/PATH propagation, hashFiles in the composite, the toolcache path and the ShellCheck hash: all exercised by the cold run 745 and the warm run 747, which found Go in the toolcache.
  • Accepted as-is:
    • The toolchain directive parse is latent, since go.mod has no toolchain line.
    • Weekly cache growth is bounded by Go's own cache trimming.
    • Release gate strength depends on protected tags, as before.

Docs

New ADR 0015. ADR 0008 is marked amended, and architecture.md → Releases links the new ADR. No design changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR

Refs #64 CI with a warm cache now takes **37 s instead of 109 s**. Release builds the four platform archives once instead of twice, and its installer tests run against the exact archives it publishes. ## What changed - **Re-saving the cache on every run (45 s each time).** The cache server matches keys case-insensitively, but setup-go compares them case-sensitively, so it never saw its own hit. A shared `.forgejo/actions/setup` action now restores two lowercase-keyed caches: Go plus the pinned linters, and the module/build caches. Go is no longer downloaded and the linters aren't rebuilt on every run. - **Cache trust.** Only pushes to `develop`/`main` save caches. PR and dispatched runs restore but never write. Before, every run saved. A PR that edits the workflow itself could still write a cache, unless the cache server scopes caches by branch (ADR 0015). The ShellCheck download is now checked against a pinned SHA-256, and the tools cache key includes a hash of the setup action. - **Superseded PR runs are cancelled.** Concurrency groups are per PR. Pushes to `develop`/`main` and dispatched runs each get their own group, so they never cancel. - **Release:** `make release VERSION=<tag>` runs first, then `make check` reuses those archives (`TEST_INSTALL_REUSE_DIST=1 TEST_INSTALL_VERSION=<tag>`). There is a new `workflow_dispatch` dry run that never publishes. The main-ancestry gate and the draft-until-all-assets-uploaded publish step are unchanged. - **Go 1.27.2 and golangci-lint v2.14.0.** govulncheck began failing on 1.27.1, with five net/http and net/textproto advisories, so every run was red. v2.13.2 can't read 1.27.2's export data. - Renovate's tool-version patterns now also match `.forgejo/actions/`. ADR 0015 records the design. ## Measurements (same runner, from job log timestamps) | | Before | After | |---|---|---| | Warm run, total | 109 s (run 703) | 37 s (run 747) | | Setup before checks | 31 s | 21 s | | Cache save after checks | 45 s | 0.5 s | | Cold run (no cache, saves both caches) | ~151 s (run 700) | 161 s (run 745) | Check time depends on what changed (run 703's checks took 27 s and run 747's 12 s, because run 747 re-ran an already-tested tree), so the like-for-like comparison is the setup and save rows. Queue time is 0 s in the API for every run, so these figures are execution time. Parallel jobs were rejected because each one would pay ~20 s of setup to save at most ~12 s. The GitHub sync job takes ~6 s every 10 minutes and was left alone. ## Verification - Local `make check` with Go 1.27.2 and golangci-lint v2.14.0: `0 issues.`, `No vulnerabilities found.`, and all 9 installer cases `PASS`. - Injected failure: commit 9fc42ad added a mis-formatted file. Run 749 failed at `fmt-check`, and the commit status was `failure`. The commit was then reverted. - Cancellation: pushing 9fc42ad cancelled run 748. Its commit shows `failure`, never `success`. - Release dry run (`workflow_dispatch`, `v0.0.0-dryrun`): runs 751 and 752 passed. They built four archives stamped with that version, all 9 installer cases passed against them, and the tag gate and Publish were skipped. The releases API lists no releases. - Tag gate checked locally: `git merge-base --is-ancestor` gives rc=1 for a branch commit and rc=0 for a commit on main. - Final head 48db339: `ci / check (pull_request)` succeeded. ## Review Three fresh-context reviews were run. - **Spec:** PASS. Added queue time to the ADR. The tools key now covers the install recipe. - **Comments:** one Renovate description was over the cap. Fixed, and two comments tightened. - **Standards:** fixed the leak of the release version into `make check` (renamed `RELEASE_VERSION`) and the overclaimed cache-trust wording. The following were dismissed with run evidence: - The `inputs.version` concern: run 751 stamped `v0.0.0-dryrun`. - Composite env/PATH propagation, `hashFiles` in the composite, the toolcache path and the ShellCheck hash: all exercised by the cold run 745 and the warm run 747, which found Go in the toolcache. - **Accepted as-is:** - The `toolchain` directive parse is latent, since go.mod has no toolchain line. - Weekly cache growth is bounded by Go's own cache trimming. - Release gate strength depends on protected tags, as before. ## Docs New ADR 0015. ADR 0008 is marked amended, and architecture.md → Releases links the new ADR. No design changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
setup-go re-uploaded its ~470 MB cache on every run because the cache
server matches keys case-insensitively and setup-go compares them
case-sensitively. A shared setup action now restores the Go toolchain,
linters and module/build caches under lowercase keys; only pushes to
develop/main save them. Superseded PR runs are cancelled. The release
job verifies the archives it publishes instead of building them twice,
and can be dispatched as a dry run.

Refs #64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
Refs #64
govulncheck now reports GO-2026-6617, -6613, -6611, -6610 and -6608 in
net/http and net/textproto on 1.27.1, which fails every CI run.

Refs #64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
v2.13.2 can't read Go 1.27.2's export data and fails typecheck on the
standard library.

Refs #64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
Revert "test(ci): TEMP let dispatched runs save caches for measurement"
Some checks failed
ci / check (pull_request) Has been cancelled
b1b18cf900
This reverts commit f46f13aeeb.
test(ci): TEMP inject a format failure
Some checks failed
ci / check (pull_request) Failing after 25s
9fc42add54
Refs #64
This reverts commit 9fc42add54.
docs(adr): record CI caching and the single release build
All checks were successful
ci / check (pull_request) Successful in 38s
dbb378c526
Refs #64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
fix(ci): address review: scope release version, key tools on recipe
All checks were successful
ci / check (pull_request) Successful in 55s
48db3396ec
Renames the release job's VERSION env so make check doesn't inherit it,
adds the setup action's hash to the tools key, and records the limits
of the push-only cache gate and the queue-time measurement in ADR 0015.

Refs #64

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmoSSUtaPYn8npGgDRRZrR
deadstyle merged commit 337443ad88 into develop 2026-10-08 22:09:46 -05:00
deadstyle deleted branch chore/64-ci-pipeline 2026-10-08 22:09:46 -05:00
Sign in to join this conversation.
No description provided.