You published v1.4.0 of a Go module with a data-corrupting bug. How do you signal consumers not to use it?
answer
- the bytes are not going anywhere
- advice, not deletion
- you must ship another version to say it
- read from the module's highest version
- the comment on the line is the message
basics
~20 sAdd a retract directive naming that version to your module's go.mod, then tag and publish a newer version carrying it. The go command reads retractions from your highest version and stops selecting the retracted one, though the code stays downloadable.
solid answer
~50 sRetraction is the module system's recall mechanism, and it is a publisher-side directive. You write `retract v1.4.0` — or a range, `retract [v1.4.0, v1.4.3]` — into your own `go.mod`, put the reason in a `//` comment on the line, and publish a *newer* version containing it. That last part is the bit people miss: the go command loads retractions from the `go.mod` of the module's highest release, so a retraction buried in an old version is never read. Once it is out, `go get module@latest` will not select a retracted version and `go list -m -u` reports the retraction with your comment. It is advisory, not enforcement: the artifact stays fetchable, and anyone whose `go.mod` already pins the retracted version keeps building until they choose to move. That makes it the right tool for a bad release and an insufficient one for a security incident.
code
mod · 8 linesmodule example.com/ourorg/parser
go 1.25
retract (
v1.4.0 // Corrupts multi-byte input; upgrade to v1.4.1.
[v1.2.0, v1.2.3] // Same bug in the v1.2 line.
)go deeper
Know the directive exists and what it is called: retract in a publisher's go.mod, naming one version or an inclusive range, with a comment saying why.
Explain the publishing mechanic — retractions are read from the module's highest version, so a new release must be tagged to carry them — and that the retracted code stays downloadable.
Show the full recall: the fixed release, the retraction, what pinned consumers see and do not see, and why an advisory is still needed when the bug has security weight.
Own the release policy: what triggers a retraction versus a quiet patch, who signs off, how the reason comment is worded for consumers, and what the organisation commits to beyond the directive.
## The constraint that shapes the answer Module versions are content-addressed and cached. Once a version has been fetched, its bytes are recorded in consumers' `go.sum` files and held in module caches and proxies. Nothing a publisher does afterwards makes those bytes stop existing. So the module system does not offer deletion; it offers **advice**, delivered through the same channel as everything else — the module's own `go.mod`. ## Writing the retraction ``` retract v1.4.0 // Corrupts multi-byte input; use v1.4.1 or later. retract ( v1.4.0 // Corrupts multi-byte input. [v1.2.0, v1.2.3] // Same bug in the earlier line. ) ``` A single version, or an inclusive `[low, high]` range for a run of bad releases. The `//` comment is not decoration: the go command surfaces it verbatim to anyone who runs into the retracted version, so it should say what is wrong and what to move to. `go mod edit -retract=v1.4.0` and `-dropretract=v1.4.0` do the same edits from a script. ## Publish it in a *newer* version The go command loads retractions for a module from the `go.mod` of that module's highest release version (and highest pre-release), not from every version it has seen. So the workflow is: 1. Fix the bug, or decide the version is simply unfit. 2. Add the `retract` line to `go.mod` on the branch you release from. 3. Tag and publish a new version — v1.4.1 — that contains it. Adding `retract v1.4.0` to v1.4.0's own history changes nothing; that file is not where retractions are read from. If the new release exists only to carry the retraction and contains no fix worth taking, it is normal to retract that version too, in the same block, so nobody upgrades into a release with nothing in it. Because retractions are still read from the highest version even when it is itself retracted, the advice keeps working. ## What consumers actually experience - `go get example.com/lib@latest` skips retracted versions and selects the highest unretracted one. - `go list -m -u example.com/lib` reports the retraction, with your comment, for a module currently resolved to a retracted version. - `go list -m -retracted -versions example.com/lib` shows retracted versions explicitly when you want to see them. - A consumer whose `go.mod` already requires v1.4.0 **keeps building**. Their build does not break, their pinned version keeps resolving, and the retraction is information, not a wall. - The bytes of v1.4.0 remain fetchable from a proxy and remain valid against `go.sum`. ## What it is not Retraction is not a security control. If v1.4.0 leaked a credential, corrupted data, or was published from a compromised account, retracting it does not stop anyone already pinned to it from running it, does not remove it from caches, and does not notify anyone who is not running a version-checking command. Handling that means publishing a fixed release, issuing an advisory through whatever channel your consumers actually read, and doing the human work of getting them to move. The retraction is a useful part of that, and never the whole of it. It is also strictly about **your own** module's versions. You cannot retract someone else's release; a `retract` line naming a different module path is not a thing the directive expresses. ## The asymmetry worth naming `replace` and `exclude` are honoured only in the main module — a dependency cannot impose them on you. `retract` is honoured from a dependency's own published `go.mod`. That asymmetry is deliberate and coherent: the first two are a consumer stating what their build should contain, so only the consumer decides; a retraction is a publisher warning about releases they own, which is exactly the kind of information a consumer wants delivered.
- Why must the retraction ship in a version higher than the one being retracted?Because the go command loads a module's retractions from the go.mod of its highest release and highest pre-release, not from every version. A retract line living only in the bad version's own history is never consulted, so the release must be followed by a newer tagged version carrying the directive.
- What happens to a team whose go.mod already requires the retracted version?Their build keeps working. The version still resolves and still verifies against go.sum. They see the retraction when they run a version-checking command such as `go list -m -u`, and an upgrade will not land them back on it. Moving off it is their deliberate action, not something forced on them.
- Is retraction sufficient when the bad release is a security problem?No. The artifact stays fetchable, anyone already pinned keeps running it, and nobody is notified unless they run a command that checks. Retract the version, publish a fixed release, and drive an advisory through a channel your consumers actually read. The directive is one part of the response, not the response.
- Can you retract a version of a module you do not publish?No. A retract directive applies only to versions of the module whose go.mod declares it, and it is read from that module's own published releases. To keep somebody else's bad version out of your own build you use a raised require floor or, narrowly, an exclude line in your main module.
saying these in an interview costs you the question
- Thinks retract deletes the version from the proxy
- Adds retract to the bad version's own go.mod only
- Expects already-pinned consumers to break immediately
- Treats retraction as sufficient security remediation
- Puts a retract line in the consuming application's go.mod