Cache Go modules GitHub Actions runs, and the file the key changed to
To cache Go modules GitHub Actions jobs already does it for you: actions/setup-go has cache defaulting to true, and it restores both the module cache and the build cache in one entry. Two details decide whether that entry ever hits: since v6.3.0 the key hashes go.mod rather than go.sum, and there are no restore keys at all, so a miss is total.

Most Go caching advice on the internet tells you to add cache: true to actions/setup-go. That input has defaulted to true since v5, so the advice is a no-op and the interesting questions are the ones underneath it: which file the key hashes, which directories get stored, when the entry is written, and what happens on a near miss.
All four answers live in about sixty lines of the action, and one of them changed recently enough that a workflow copied from a blog post in 2025 now behaves differently from the same workflow today. This page walks the key as the action builds it, names the version where the file it hashes changed, and shows the warning you get when it cannot build a key at all.
What the action caches, and what the key is made of
The action asks Go where its directories are rather than hard-coding paths: it runs go env GOMODCACHE and go env GOCACHE and caches both. That pairing is the whole reason the action is worth using over a hand-rolled actions/cache step on ~/go/pkg/mod. The module cache saves the download and the build cache saves the compile, and on a project of any size the compile is the larger number.
The key is then assembled from five pieces in a fixed order: a setup-go prefix, the runner OS, the Node process architecture, the image identifier on Linux, the Go version spec you asked for, and a hash of one dependency file. Nothing about your repository beyond that file enters the key, which is why two branches with the same dependencies share an entry and why bumping a patch Go version invalidates everything.
| Piece of the key | Where it comes from | What changes it |
|---|---|---|
setup-go- | A literal prefix in the action | Nothing |
| The platform | RUNNER_OS | Moving a job from Linux to macOS or Windows |
| The architecture | Node's process.arch on the runner | Moving between x64 and arm64 runners |
| The image, on Linux only | The ImageOS variable the runner sets | A monthly runner image roll, which is out of your hands |
| The Go version spec | The go-version you asked for, parsed | Any change to the requested version, including a patch |
| The dependency hash | hashFiles over one file: go.mod from v6.3.0, go.sum before that | Editing that file, and on older versions only that file |
The file it hashes changed in v6.3.0
Until v6.2.0 the action hashed go.sum. From v6.3.0 onward, and in v7, it hashes go.mod. The change is a single constant in src/package-managers.ts, and the release notes for v6.3.0 name it as "Update default Go module caching to use go.mod".
That is a smaller change than it sounds for most repositories and a real one for two kinds. A project with no third-party dependencies has no go.sum at all, and on the old default the action could not build a key and gave up; on the new default it works. A project that pins by go.sum and edits it independently of go.mod, which happens when only a transitive checksum moves, now keeps the same key across that edit and restores a module cache that is missing the new checksum. Go will fetch what is missing, so it is slower rather than broken.
If you want the old behavior, ask for it rather than pinning an old action. cache-dependency-path overrides the file entirely, takes a multi-line list, and is what a monorepo with several modules needs anyway.
- uses: actions/setup-go@v7
with:
go-version: '1.26'
# cache defaults to true; this is here to be explicit, not to enable it
cache: true
cache-dependency-path: |
go.sum
tools/go.sum
services/api/go.sumThere are no restore keys, so a miss is a total miss
This is the behavior that surprises people who know actions/cache, and it is worth stating plainly because no amount of key tuning works around it. The action calls the toolkit restore with a primary key and nothing else. There is no restore-keys list, no prefix fallback, no partial match.
So a Go cache on GitHub Actions is binary. Change one line in the hashed file and you do not get last week's modules plus the delta: you get an empty GOMODCACHE and an empty GOCACHE, and the job downloads and compiles everything. On a repository where dependencies change weekly that is fine. On one where they change daily, the action is doing almost nothing for you and a hand-rolled actions/cache step with restore-keys will beat it.
The same all-or-nothing property is why the ImageOS component matters more than its size suggests. A runner image roll changes the key for every Go repository in your organization on the same morning, and every one of them takes a full cold build once. If you see a Monday where everything was slow and nothing was deployed, that is usually what it was rather than anything you did.
- uses: actions/setup-go@v7
with:
go-version: '1.26'
cache: false
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-${{ runner.os }}-1.26-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-1.26-
go-${{ runner.os }}-The warning that looks like a failure
When the action cannot build a key it does not fail the job. main.ts wraps the whole restore in a try block and turns anything thrown into core.warning, so the step goes green, the annotation goes in the summary, and the job runs uncached at full cold cost. A pipeline can be silently paying for a cold Go build on every run for months with nothing red anywhere.
Two throws end up behind that warning, and they mean different things. If the dependency file is missing from the workspace root you get the message below. If a cache-dependency-path you set matched nothing, you get "Some specified paths were not resolved, unable to cache dependencies" instead, which is the case where a glob is wrong rather than a file being absent.
Restore cache failed: Dependencies file is not found in /home/runner/work/lethe/lethe. Supported file pattern: go.sumWhen the entry is written, and when it is not
The save runs as a post step, and its post-if is success(). A job that fails does not write a cache entry, which is usually what you want and is occasionally the reason a repository never warms up: if your flaky test suite fails half the time, half your runs contribute nothing to the cache.
Two more conditions silently skip the write. If the restore was an exact hit the action logs "Cache hit occurred on the primary key" and saves nothing, which is correct and is why a warm repository shows no upload. And if the toolkit returns a cache id of -1, which happens on a reservation collision or when the job token cannot write, the action traces it and moves on without a warning. That last case is the normal one on a pull request from a fork, where the token is read-only by design.
The other write you cannot see is on a branch. Actions cache entries are scoped: a run restores from its own branch and from the default branch, and a pull request also reads its base branch. If nothing ever runs on your default branch, nothing there is ever warm, and every feature branch starts cold no matter how well the key is written. A small scheduled build on the default branch fixes that for free, and GitHub Actions cache not restored covers the rest of the scoping rules.
What to check, in order
- Check the action version before you change anything, because v6.2.0 and v6.3.0 hash different files and that alone explains a cache that went cold with no workflow edit.
- Look for
Cache is not foundin the setup step of a run you expected to be warm. That is the action telling you the primary key missed, and with no restore keys behind it the job is about to do everything from scratch. - Search the run summary for
Restore cache failed. It is a warning, so nothing is red, and it means you have been paying full price on every run. - Confirm something runs on your default branch. A feature-branch-only pipeline has nothing to restore from.
- If your dependencies genuinely change most days, stop tuning
setup-goand write anactions/cachestep with restore keys, because partial restores are the only thing that helps at that rate of change.
Frequently asked questions
Do I need to set cache: true on actions/setup-go?
cache input has defaulted to true since v5, so adding it changes nothing. What is worth setting is cache-dependency-path, which overrides the file the key hashes and accepts a multi-line list. That is the input a monorepo with several modules actually needs, and the one that lets you keep hashing go.sum on a version that now defaults to go.mod.Does setup-go cache the Go build cache as well as modules?
go env GOMODCACHE and go env GOCACHE and caches whatever those report, so the download and the compiled objects are restored together. That pairing is the main reason to prefer it over a hand-rolled cache on the module directory alone, where the modules come back warm and every package still recompiles.Why is my Go cache cold when nothing in go.mod changed?
ImageOS value the runner sets. A monthly runner image roll changes that last one for every repository at once, and because the action uses no restore keys the result is a full cold build rather than a partial restore.What does "Restore cache failed: Dependencies file is not found" mean?
main.ts, which catches everything the restore throws, so the step stays green and the job runs at full cold cost. The file it names is whichever pattern that action version uses: go.sum before v6.3.0 and go.mod from v6.3.0 onward.Should I use setup-go caching or actions/cache for Go?
setup-go unless your dependencies change most days. It picks the right two directories, keys them sensibly and needs no maintenance. Its weakness is that it restores on an exact key match only, with no restore keys, so a repository whose go.mod moves daily gets a cold build daily. That is the case where a hand-written actions/cache step with prefix restore keys wins.Related guides
References
- actions/setup-go: cache key assembly in src/cache-restore.ts (verified 2026-09-21)
- actions/setup-go v6.3.0 release notes: default Go module caching moves to go.mod (verified 2026-09-21)
- GitHub Docs: dependency caching reference, key matching and cache scope (verified 2026-09-21)
- kuoss/lethe#83, the run whose warning annotation is quoted on this page (verified 2026-09-21)
- GitHub Actions documentation