From CloudX | Ad Infrastructure for the Intelligence Era · · 3m
How CloudX cut CI test times by 69% with a simple swap
Replacing GitHub's setup-go action with a smarter caching strategy eliminated 86% of unnecessary test runs.
In brief
Replacing GitHub's setup-go action with a smarter caching strategy eliminated 86% of unnecessary test runs. Parallel CI jobs need separate cache keys to avoid racing and loading stale data that forces unnecessary retesting. Originally reported by CloudX | Ad Infrastructure for the Intelligence Era.
A drop-in replacement for setup-go
CloudX built a new GitHub Action that cut test job runtimes by 69%. They're open-sourcing it so other teams can get the same speedup.
The default action wastes effort
GitHub's official actions/setup-go makes parallel jobs interfere with each other. Backtesting shows 86% of the work it does is unnecessary.
“86% of the work the default action does is completely unnecessary.”
Fast CI enables fast shipping

CloudX invests heavily in CI speed because reliable, quick tests let teams build fast without breaking things for customers.
The 90-second goal
When you push code to CloudX, the goal is a clear answer on whether it builds, passes tests, and meets linter rules within 90 seconds.
Algorithmic improvements matter most
CloudX already uses Warp Build for fast machines. As their test suite scaled, they realized actions/setup-go wasn't setting them up for success.
The cache key is incomplete
The default cache key only includes OS, architecture, Go version, and go.mod hash. Most code changes don't modify any of these.
Stale caches slow everything down

The first job run persists cache state. Until go.mod changes, every run loads that first value, which gets staler as you change code.
Parallel jobs race each other
Multiple parallel jobs running setup-go race to write different cache states. The winner's cache might not help the other jobs at all.
When lint wins, tests lose
If a lint job finishes first, it saves a cache without test results. Subsequent test jobs keep using that stale value and re-run tests unnecessarily.
Go's toolchain caches are smart
The Go toolchain saves module downloads, build artifacts, and test results. It only recomputes when inputs actually change.
Three caches work together
The module cache stores downloaded source code. The build cache stores package archives. The test cache stores test outputs and exit codes.
GitHub Actions cache is different
GitHub's cache uses immutable key-value pairs. It only writes a new blob if the job succeeds and there was no exact key match initially.
Job identity solves the race
cloudx-io/setup-go includes job identity in the cache key. Lint and test jobs now save and restore separate caches without conflicts.
Save cache on every run

The new action writes a cache entry every time by including the run ID in the key. This preserves incremental work instead of discarding it.
The real-world impact

When CloudX switched actions, median test runtime fell from 131 seconds to 41 seconds. That's a 69% improvement from one configuration change.
“cloudx-io/setup-go immediately cut test job runtimes by 69%.”
Fresh caches mean fewer tests
With the new strategy, the loaded cache is always fresh from the run before. Tests only run for packages genuinely modified by each commit.
86% reduction in test package runs
Backtesting 4,000 commits showed the new approach eliminated 86% of test package runs compared to the default action's behavior.
Two downsides to consider
Cache blobs can grow large since they're saved more often. You may need expanded GitHub Actions cache capacity, though faster jobs offset the cost.
The takeaway
Parallel CI jobs need separate cache keys to avoid racing and loading stale data that forces unnecessary retesting.

