Your Go build still selects v1.4.1 of a transitive module although v1.4.3 exists — why, and how do you confirm it?
answer
- the resolver never looks at the tag list
- a release nobody requires is invisible
- compare selected against published
- the deciding require line is not yours
- ask the binary what it actually shipped
basics
~20 sBecause no go.mod in the module graph names v1.4.3. Minimal version selection reads requirements, never the upstream tag list, so a published release is invisible until something requires it. Compare the selected build list against the versions that actually exist.
solid answer
~50 sThis is minimal version selection working exactly as designed: a release enters a build only when some `go.mod` in the graph states it as a minimum. v1.4.3 exists upstream, nothing requires it, so the maximum of the minimums is still v1.4.1 and the build never looks further. I confirm it in three steps: `go list -m all` for what is actually selected, `go list -m -versions example.com/lib` for what is published, and `go mod why -m example.com/lib` for which import drags the module in at all. For a binary already in production, `go version -m ./app` reports the module versions built into it, which is the only evidence that matters during an incident. Getting v1.4.3 in is then an explicit edit — raising the minimum in the main module's `go.mod` — not something a rebuild will do for me.
code
text · 11 lines$ go list -m example.com/lib
example.com/lib v1.4.1
$ go list -m -versions example.com/lib
example.com/lib v1.4.0 v1.4.1 v1.4.2 v1.4.3
$ go mod why -m example.com/lib
# example.com/lib
example.com/app/internal/report
example.com/reporting/render
example.com/lib/formatgo deeper
Know that a published release does nothing until a go.mod requires it, and that go list -m all shows what your build actually selected.
Explain that the deciding requirement for a transitive module lives in a dependency's go.mod, and be able to name commands that separate 'what is selected' from 'what exists upstream'.
Demonstrate the diagnosis end to end under time pressure, including reading module versions out of the deployed binary, and articulate why no cache or rebuild would have changed the answer.
Own the process gap this exposes: determinism means nothing raises your floors but you, so describe the cadence, ownership and evidence trail that keep a fleet of services from silently ageing.
## Nothing is broken The first thing to say out loud is that this is not a bug, a stale cache or a proxy problem. Go's resolver takes **the maximum of the minimum versions required across the module graph**. The set of tags that exist upstream is simply not an input to that computation. If every `go.mod` reachable from your main module says at most `example.com/lib v1.4.1`, then v1.4.1 is the answer, and it will keep being the answer through any number of clean rebuilds, cleared caches and CI runs. This is the deliberate consequence of the design: builds do not drift. It is also its operational cost: **published fixes do not arrive by themselves**, and a green build is not evidence of currency. The transitive case is the one that surprises people, because there is no line in *their* `go.mod` to look at. The requirement lives in a dependency's `go.mod` — someone else's file, fetched into the module cache — and it is that file's number which sets the floor. ## Confirming it, in order **1. What is actually selected?** ``` go list -m example.com/lib ``` prints the module path and the version minimal version selection chose. `go list -m all` prints the whole build list, which is the right artefact to attach to a ticket. **2. What exists upstream?** ``` go list -m -versions example.com/lib ``` lists the versions the module's origin actually publishes. Comparing this against step 1, module by module, is the honest answer to "are we behind, and by how much?" — it is the only comparison that distinguishes "we chose not to move" from "we never noticed". **3. Why is the module here at all, and who set the floor?** ``` go mod why -m example.com/lib ``` shows a shortest import path from a package in the main module to a package in that module, so you learn *which* of your dependencies is responsible for it being in the graph. That tells you whose `go.mod` you would need to see move for the floor to rise on its own. **4. What did the running binary actually ship?** ``` go version -m ./app ``` reads the module information embedded in a compiled Go binary and prints the module versions it was built with. During an incident this beats every other source, because it describes the artefact in production rather than what a fresh resolution would produce today. ## Making the fix arrive Since only a requirement can raise a floor, the fix is to state one. Asking the go command for the specific version — `go get example.com/[email protected]` — writes a requirement into the main module's `go.mod`, typically marked `// indirect` because you do not import the module directly. From that moment the maximum of the minimums is v1.4.3 and every build, everywhere, uses it. That line is reviewable in a pull request, which is precisely the property the design is buying. There is a subtlety worth knowing: your added requirement raises the floor for **your** build only. A library you publish raises it for consumers too, which is why library authors should think of their stated minimums as a compatibility commitment rather than a private convenience. ## The organisational half Because the language will never move you, somebody must. The failure mode this question is really about is a service quietly running an old transitive module for eighteen months while every build passes. Teams that handle this well have an explicit, scheduled activity that: - diffs the selected build list against published versions on a cadence; - treats "a fix exists upstream that our graph does not name" as a normal finding rather than an alarm; - lands the raised minimums as ordinary reviewed changes with tests, not as an emergency; - and keeps the record of what shipped by reading the binaries, not the source tree. ## What not to conclude - Do not blame the module cache or a proxy. Clearing caches changes nothing here; the same requirements produce the same maximum. - Do not assume a rebuild in CI resolves differently from your laptop. It does not — that determinism is the whole point. - Do not read your own `go.mod` alone and conclude the version is unexplained. For a transitive module the deciding requirement is in a dependency's file. - Do not treat the absence of a version in your `go.mod` as meaning the module is not in your build. Selected and stated are different lists, and `go list -m all` is the one that ships.
- How would you prove to an auditor which version a running service was built with?Read it from the artefact: `go version -m ./app` prints the module versions embedded in a compiled Go binary, including transitive ones. Resolving the source tree today can legitimately give a different answer if requirements changed since the build, so the binary is the authoritative record of what shipped.
- Does clearing the module cache or forcing a fresh fetch change the selected version?No. Selection depends only on the requirements stated in the go.mod files of the graph; caching affects where the bytes come from, not which version is chosen. A clean machine with an empty cache computes the same build list, which is exactly the reproducibility the design promises.
- Why does your own go.mod end up holding an `// indirect` line for a module you never import?Because you stated a minimum for it. Once you ask for a specific version of a transitive module, that requirement has to be recorded somewhere in the graph, and the only file you control is the main module's go.mod. The `// indirect` marker records that no package in your module imports it directly.
saying these in an interview costs you the question
- Blames a stale module cache or a proxy
- Expects a rebuild in CI to pick up the newer release
- Reads only the main go.mod and calls the version unexplained
- Assumes patch releases are applied automatically
- Treats a green build as evidence dependencies are current