A GitHub release v1.2.0 shipped a broken artifact — do you retag it or ship v1.2.1?
answer
- Someone already fetched it
- Two artifacts, one name, no error
- Numbers are cheap, ambiguity is not
- Mark it, do not erase it
- Check what the badge now points at
basics
~20 sShip v1.2.1. A published version is a fact other people have already fetched and cached, so replacing its contents makes two different artifacts share one name. Burn the bad version, mark it clearly, and move Latest to the good one.
solid answer
~50 sOnce `v1.2.0` is public, the tag and its assets are in caches, CI logs, lockfiles and mirrors. Deleting the release and re-pushing the tag means anyone who already fetched has different bytes under the same name, and nothing tells them — that is the worst failure mode there is, because it is silent and unreproducible. So: cut `v1.2.1` from a fixed commit and publish it as a normal release. Then contain `v1.2.0` — edit its notes to say plainly that it is broken and superseded, and if it is actively dangerous, delete its assets so a download fails loudly rather than installing a bad binary. Keep the tag: removing it breaks every reference that already points at it. Make sure Latest resolves to `v1.2.1` (`make_latest: "true"`), and if the fix is on an older line, publish with `--latest=false` so it does not steal Latest from a newer major.
go deeper
Know the rule: once a version is published you ship a new patch version rather than replacing the contents of the old one, because other people already have the old bytes.
Explain why moving a published tag is dangerous — caches, lockfiles and mirrors keep the old artifact under the same name, so two different builds answer to one version with no error anywhere.
Walk the full recovery: publish the patch, annotate the bad release, decide whether to delete its assets, keep the tag, and verify what Latest now resolves to, including the maintenance-line case.
Own release immutability as an organisational policy and the prevention that makes it cheap: draft staging, prereleases that soak, restricted tag creation, and published checksums so consumers can detect a swap at all.
## The principle A published version is an **immutable public fact**. The moment `v1.2.0` exists, artifacts and refs propagate outward into places you do not control: developer machines, CI caches, container layers, mirrors, lockfiles, corporate proxies, and incident notes written by people who will read them next year. Changing what that name refers to does not update those copies — it creates a world where two different sets of bytes answer to the same version, and no error is raised anywhere. Debugging "works on my machine" against a moved tag is genuinely one of the nastiest classes of problem in software delivery, because your first instinct — compare versions — reports that they match. So the answer is essentially never "retag", and an interviewer is checking whether you reach for the convenient fix or the correct one. ## What to do instead **1. Fix forward.** Commit the fix, tag `v1.2.1`, let the pipeline build and publish it. The bad number is burned; version numbers are cheap and confusion is not. **2. Mark the bad release loudly.** Edit `v1.2.0`'s release notes with a clear first line: broken, why, superseded by `v1.2.1`. That body is what someone landing from a search result or a stale link reads. **3. Decide about the assets.** If the artifact merely has a bug, leaving it downloadable is fine and preserves reproducibility for anyone investigating. If it is actively harmful — a data-corrupting bug, a leaked credential baked into the image, a supply-chain concern — delete the release assets so the download 404s. A loud failure beats a silent bad install. Note what deleting does *not* do: it does not recall anything already downloaded, and it does not reach mirrors. **4. Keep the tag.** Deleting `v1.2.0`'s tag breaks every permalink, every `git describe` output, every build that pins it, and every audit trail entry — while achieving nothing, because the bad artifact was already fetched by whoever fetched it. **5. Fix the Latest pointer.** Publish `v1.2.1` with `make_latest: "true"` so the badge, the `/releases/latest` endpoint and the install scripts that follow it resolve to the good version. If the fix is on a maintenance line and a newer major exists, publish with `--latest=false` instead — you want Latest to stay on the newest stable, not to drag everyone backwards. **6. Communicate at the right blast radius.** Release notes for the general case; a direct message to known consumers when the bug is severe. If the artifact was published to a registry as well as attached to a release, the registry copy has its own rules and its own removal semantics — handle it deliberately rather than assuming fixing the GitHub release covers it. ## The narrow exception If the release was published minutes ago, is still unnoticed, and you can be genuinely confident nothing fetched it, deleting and recreating is defensible. Be honest about how weak that confidence is: automated dependency scanners, mirrors and notification-driven pipelines can move in seconds. The cost asymmetry is stark — an extra patch number costs nothing, an ambiguous version costs somebody a day. ## Prevention, which is what the follow-up will probe - **Draft-then-publish**: build and attach every asset to a draft, verify, then flip to published. Most "broken release" incidents are actually *incomplete* releases caught too late. - **Prereleases**: ship `v1.2.0-rc.1` with `prerelease: true` so it is excluded from Latest, let it soak, then cut the real tag. - **Restrict tag creation** with a GitHub ruleset targeting `v*` so a release is an intentional, authorised act. - **Publish checksums as assets** alongside binaries. If consumers verify, a swapped artifact is detectable rather than invisible — which is the whole reason the immutability rule exists. A candidate who says "ship the patch, mark the old one, keep the tag, move Latest" and then talks about preventing the next one has operated real software.
- When is deleting the release assets the right call rather than leaving them?When the artifact is actively harmful — corrupts data, embeds a leaked credential, or carries a compromised dependency. A failing download is a loud, diagnosable error; a silently installed bad binary is not. For an ordinary bug, leaving the assets preserves reproducibility for anyone investigating an incident that involved that version.
- Why keep the v1.2.0 tag even though the release is bad?Because the tag is referenced far beyond the release page — permalinks, pinned builds, `git describe` output, incident notes, audit trails. Deleting it breaks all of those and recalls nothing, since anyone who fetched the artifact already has it. Retiring a version is a communication act, not a deletion act.
- How do you stop the same class of incident recurring?Stage releases as drafts so every asset is attached and verified before anything is visible, ship release candidates with the prerelease flag so they are excluded from Latest while they soak, restrict who may push version tags with a ruleset, and publish checksum files as assets so a mismatched artifact is detectable rather than silent.
- The fix lands on the 1.2 maintenance line while 2.0 is current. What changes?Publish `v1.2.1` with `--latest=false` (or `make_latest: "false"`) so the Latest badge and the `/releases/latest` endpoint keep resolving to the 2.0 line. Otherwise every installer that follows Latest silently downgrades a major version, which fails in confusing ways because nothing reports an error.
saying these in an interview costs you the question
- Deletes the tag and re-pushes it at a new commit
- Says nobody could have downloaded it yet
- Edits the release assets in place and moves on
- Deletes the whole release, breaking every existing link
- Forgets that Latest may still point at the broken version