Repository navigation
Conversation
Every goCI job sets up Go with `cache: true` and the same go.sum-based key, so only the first job to finish can save it. That is usually govulncheck; test, build and CodeQL then log "Unable to reserve cache ... another job may be creating this cache". The test job's -race/coverage build output is never stored, and it recompiles everything on every run even when the cache "hits". goTest now turns off setup-go's cache and restores/saves GOCACHE and GOMODCACHE itself, under a key of its own: an optional suffix, OS, ImageOS, arch, Go version and the go.sum/go.work.sum hash, with a prefix restore-key so a go.sum change starts warm. Only the default branch saves, so feature branches stop filling the cache quota with near-duplicates. Test results are expired with `go clean -testcache` before saving, so restored caches never replay a pass for tests that depend on Docker, the network or the clock. goCI gains a `test-cache-key-suffix` input for repositories that call it more than once (for example a separate e2e job), so each test job keeps its own entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trial results on smallstep/inventory: the test job is about 66% shorter with a warm cacheSetup. smallstep/inventory#633 pointed test (stable) job
Three runs is a small sample. Even so, the slowest warm run beats the fastest baseline run on every metric. What the baseline shows about the bugWith setup-go's shared cache today, the baseline's compile median is 469 s on a "hit" (n=21) and 510 s on a miss (n=9). The hit restores govulncheck's ~318 MB cache, which barely helps the Behavior checks
Caveats
🤖 Generated with Claude Code |
Problem
Every goCI job (
goTest,goBuild,govulncheck,codeql-analysis) sets up Go withcache: trueandcache-dependency-path: "**/go.sum". setup-go's key issetup-go-<os>-<arch>-<image>-go-<ver>-<go.sum hash>, the same in every job, and a key can only be saved once. govulncheck usually finishes first and saves. Test, build and CodeQL then log:So the test job's
-race/coverage build output is never stored. On a "hit" it restores govulncheck's cache, which doesn't include that output, and recompiles from scratch.Measured on smallstep/inventory across all 205 successful "CI & Upload" runs since 2026-07-10:
Run Test Suitehas a median of 886 s whether setup-go's cache hit (n=154) or missed (n=51). About 4–7 minutes of that is compiling before the first test result. Jobs that save their own cache show what's possible: inventory's lint goes from 487 s to 39 s, and upload-image'sdocker-makefrom 303 s to 8 s.Change
goTest.ymlcacheon setup-go.GOCACHEandGOMODCACHEwithactions/cache/restoreandactions/cache/save(v6.1.0, SHA-pinned). Paths come fromgo env, so macOS and Windows runners work too.go-test-[<suffix>-]<RUNNER_OS>-<ImageOS>-<RUNNER_ARCH>-<GOVERSION>-<hash of go.sum, go.work.sum>.ImageOSkeeps cgo objects from outliving the system headers they were built against.GOVERSIONseparates thestableandoldstablematrix entries.github.ref == refs/heads/<default>) and only when the key didn't match exactly. Feature branches restore main's entry instead of each storing a ~500 MB near-duplicate. inventory's caches are currently 10.53 GB across 80 entries, over the 10 GB repo limit.go clean -testcachebefore saving. GOCACHE also holds test results, and the defaultgotestsumcommand is cacheable. Without this, restored caches would replay(cached)passes for tests whose outcome depends on things Go doesn't track: Docker/testcontainers, the network, the clock. With it, the build output is reused and every test still runs.cache-key-suffixinput. It rejects commas and newlines, which would break the key.goCI.yml: addstest-cache-key-suffixand passes it through. It's needed when a repo calls goCI more than once, for example a separate e2e job. Inside a reusable workflowgithub.jobis alwaystest, so without it two callers would share a key and the first to finish would win, which is the same bug again.AGENTS.md: documents the input and the caching behavior.The other jobs (
goBuild,govulncheck,codeql-analysis) still share setup-go's key with each other. The test job no longer competes for it. Giving those jobs their own caches can be a follow-up.Things to know
go-test-…entry per Go version alongside their existing setup-go entries, on the default branch only. Go trims GOCACHE entries unused for 5 days. GOMODCACHE isn't trimmed, but it resets when the Go version changes.Checks
actionlint(the pinned 1.7.11 image, as in ci.yml): 0 errors. Same ignored-error counts asmain.zizmor1.30.1 (--min-severity medium --min-confidence medium, online): no findings. Output is identical tomain.Test plan
Opened as a draft. Callers use
@main, so merging changes every goCI caller immediately. Following AGENTS.md:@ci/go-test-build-cache-trial, which is this branch plus one trial-only commit that lets the cache save from any ref. A feature branch can't otherwise warm a cache.github.ref == refs/heads/<default>) itself: not exercised yet. Check the firstmainrun after merge.mainentry first, so this also has to wait until after merge.(cached)test results in gotestsum output: 0 in all four runs, each "DONE 2015 tests".ci/go-test-build-cache-trialbranch and close smallstep/inventory#633 (the consumer never changed@mainon its default branch).🤖 Generated with Claude Code