skip to content

What is in the Go module cache (GOMODCACHE), and when is go clean -modcache the right fix?

level: seniorimportance: nice to knowfreq 34%

answer

  1. one directory shared by every build
  2. extracted trees plus raw downloads
  3. read-only on purpose
  4. clearing it re-downloads, it does not re-trust
  5. same failure after clearing means not local

basics

~20 s

GOMODCACHE holds every downloaded module version, extracted read-only, plus the raw files as fetched. Clearing it forces a fresh download: the right fix for a corrupted local cache, and no fix for a checksum mismatch.

solid answer

~50 s

`GOMODCACHE` — by default `$GOPATH/pkg/mod` — is a machine-wide store shared by every module you build. It holds extracted module trees, made **read-only** on purpose so that an accidental edit cannot become an invisible local patch affecting every build on the box, plus a `cache/download` directory of the raw `.info`, `.mod` and `.zip` files as fetched. `go mod verify` re-hashes those extracted trees against `go.sum` and tells you whether anything on the machine changed after download. `go clean -modcache` removes the whole thing. It is the correct move for a cache corrupted by a killed download or a CI cache archive restored mid-write, because the next build re-fetches and re-verifies. It is the wrong move for a checksum mismatch: verification is against `go.sum`, not against the cache, so a clean re-download fails identically — and that identical failure is exactly the useful signal that the problem is not local.

code

text · 8 lines
text
$ go env GOMODCACHE
/Users/dev/go/pkg/mod

# offline: re-hash what is extracted here against go.sum
$ go mod verify

# heavy: delete the whole cache so the next build re-downloads and re-verifies
$ go clean -modcache

go deeper

for a junior

Be ready to say where downloaded modules live, that the directory is shared across all your projects, and that go clean -modcache deletes it so the next build downloads everything again.

for a middle

Explain the two halves of the cache — extracted trees and the raw download store — and why the files are read-only. Be clear that verification always compares against go.sum, never against the cache.

for a senior

Show the triage: use go mod verify and a cache clear to separate local corruption from a problem that is the same everywhere, and be able to say why a mismatch surviving a fresh download rules the machine out entirely.

for a principal

Own the CI story around a shared cache — how it is saved and restored, why a routine wipe in the pipeline indicates a caching bug rather than a fix, and what you forbid engineers from doing to make a verification failure go quiet.

## What lives in the module cache `GOMODCACHE` names one directory, defaulting to `$GOPATH/pkg/mod`, shared by every Go module on the machine. Inside it are two distinct things: 1. **Extracted module trees**, under paths that encode the module path and version, e.g. `.../golang.org/x/[email protected]/`. These are the sources the compiler actually reads. 2. **A download store**, under `cache/download`, holding the raw files as fetched from the module source — the `.info`, `.mod` and `.zip` for each version, plus the recorded hashes and lock files. Its layout mirrors the module proxy protocol, which is why the directory can itself be served as a proxy: pointing GOPROXY at it with a `file://` URL gives you a completely offline module source built from what a machine has already downloaded. The practical consequence of one machine-wide cache is that a first build in a fresh container is slow and every build after it is fast — and that CI systems cache this directory between runs, which is where most of its failure modes come from. ## Why the files are read-only Extracted module files are created without write permission. This is not paranoia about attackers; it is about a specific, very costly accident: a quick edit to a dependency "just to test something" would silently apply to every build on that machine, would not appear in any diff, and would vanish the moment the cache was cleared. The build would be unreproducible in a way nobody could see. Read-only turns that mistake into an immediate error. The permissions also mean the directory cannot be deleted with a naive recursive remove without fighting the mode bits — which is precisely why `go clean -modcache` exists rather than an instruction to delete the folder. (A `-modcacherw` build flag makes newly written cache files writable, for the rare tooling that needs it; it is a deliberate trade, not a default.) ## Two checks that are easy to confuse - **`go mod verify`** re-hashes what is already extracted in the cache and compares against `go.sum`. It is offline and answers a narrow question: *has anything on this machine changed since it was downloaded?* A modified file, a truncated extraction, a tool that rewrote something. - **Download-time verification** happens on every fetch: the go command hashes what it received and compares it against `go.sum` (consulting the checksum database when there is no recorded line yet). This answers: *is the content I just received the content everyone expects?* Both compare against `go.sum`. Neither compares against the cache as an authority — the cache is never the source of truth. ## When `go clean -modcache` is right It removes the entire cache, so the next build downloads and verifies everything again. Reach for it when the evidence points at **local state**: - a download killed part-way, leaving a partially written zip or a half-extracted tree; - a CI cache archive that was saved while a build was still writing, then restored on a later run; - a machine where several toolchains or containers have been writing to the same shared path; - `go mod verify` reporting a specific module as changed on one machine while every other machine is fine. It is heavy — the next build re-downloads everything — so on CI it is a targeted intervention, and if it becomes a routine step in the pipeline, the real bug is in how the cache is saved and restored, not in the modules. ## When it is not the fix A `go.sum` mismatch is the case people most often reach for it, and it does not help. Verification is against the recorded hash, so a clean re-download produces the same mismatch. What the clean cache *does* buy you is a clean answer to one triage question: **is this local or not?** If the mismatch survives a fresh download on a fresh machine, the local cache was never involved, and the question moves to what was published and what `go.sum` records — a security-review conversation, not a cache one. The destructive responses to resist in the meantime are all attempts to make the tooling stop complaining: deleting the go.sum line, running a command that rewrites it, or setting the go command to write module files instead of reading them read-only. Each of them converts a loud, precise failure into a green build with an unanswered question behind it. When an organisation-wide build failure starts at the same minute everywhere, the fact that it *is* everywhere is the most useful thing you know: the same recorded hash and the same downloaded content on every machine means the divergence is upstream, and the first thing to preserve is the exact error text and the module version it names.

  • How can the module cache itself act as a module source for an offline build?
    The `cache/download` subdirectory is laid out in the module proxy protocol's own format, so pointing GOPROXY at it with a `file://` URL turns a machine's existing downloads into a complete offline module source. It is a neat way to build in a network-isolated environment seeded from a machine that already fetched everything the build needs.
  • A checksum mismatch appears on every build machine in the company at the same minute. What does that pattern tell you?
    That nothing local is involved. Every machine holds the same recorded hash and receives the same content, so a simultaneous, uniform failure means the divergence is upstream of all of them. Clearing caches will reproduce it identically. Preserve the exact error and the module version it names, stop builds from being 'fixed' by rewriting go.sum, and escalate it as a content-integrity question.
  • Why not just make the module cache writable so tools can patch dependencies in place?
    Because the cache is shared by every module on the machine, so a patch there applies invisibly to every build and appears in no diff. The build becomes unreproducible in a way that is very hard to see, and the patch disappears the moment the cache is cleared. Local changes to a dependency belong in the repository, where they are reviewable.

It is a shared parts bin bolted shut: you can take a part out, but you cannot file down an edge and leave it in there for everyone else to build with.

saying these in an interview costs you the question

  • Reaches for go clean -modcache to fix a checksum mismatch
  • Thinks the module cache is per-project rather than machine-wide
  • Deletes the go.sum line to make a mismatch go away
  • Believes go mod verify contacts the network
  • Treats a routine cache wipe in CI as a solution rather than a symptom