A release can be technically successful while shipping the wrong binary: the tag points to the right commit, GitHub reports every upload complete, and a Linux user receives a macOS archive. With immutable releases enabled, discovering that mistake 10 minutes later does not create a “replace file” option—it creates a versioning and communication incident.

The useful mental shift is to stop treating publication as the final upload step. It is the point at which a repository turns a build output, a Git reference, and a support promise into a permanent public record. GitHub’s immutable releases protect that record by preventing post-publication changes to release assets and the associated Git tag.

That protection is valuable precisely because it removes the easy recovery moves teams use under pressure. This playbook separates mistakes you can still correct in release metadata from mistakes that require a new version, a new tag, and an explicit advisory to users.

1. Define the release boundary before someone presses Publish

A GitHub release is based on a Git tag, but the tag date and the release publication date can differ. That distinction matters during an incident: a tag created on Monday and a release published on Thursday are still tied to one specific point in repository history, even though the public announcement happened later.

For an immutable release, treat these five items as one indivisible publication unit:

  • Version: the user-facing identifier, such as v2.4.1.
  • Tag: the Git reference that must resolve to the intended commit SHA.
  • Assets: archives, installers, binaries, SBOMs, signatures, and checksum files.
  • Checksums: the hashes users and automation can compare after downloading.
  • Release notes: the upgrade instructions, fixes, known limitations, and security guidance.

If any one of these is wrong, the release is not “mostly right.” A correct v2.4.0 binary with a checksum generated from an earlier build is unusable for a cautious downloader. Conversely, a perfect archive attached to a tag pointing at the wrong commit makes source verification misleading.

Assign one person to make the final publish decision, even when CI creates the artifacts. The build system can attest to a successful job; it cannot decide whether mytool_2.4.0_linux_arm64.tar.gz is really the file your ARM64 users should install.

2. Run a release gate, not a last-minute upload checklist

The most effective checkpoint occurs after artifacts exist but before the release is published. At that point, every file is available for inspection, while a version number, tag, and asset set are still cheap to correct.

Use a release gate with an explicit pass or stop outcome. A reviewer should be able to answer each item without reconstructing context from CI logs.

Check Pass condition Stop publication when
Version The version matches the planned changelog and package metadata. The archive, release title, or package manifest uses different versions.
Tag target The tag resolves to the reviewed release commit SHA. The tag was created from a local branch without confirming its target.
Asset names Each filename states product, version, operating system, and architecture where applicable. Two artifacts can be confused, such as linux-amd64 and linux-arm64.
Checksums Every downloadable binary or archive appears in the checksum file. A checksum file was copied from an earlier build or omits a release asset.
Notes Upgrade, compatibility, and known-risk statements match the shipped code. The notes promise a fix that did not make the tagged commit.

The overlooked cost is reviewer fatigue. Five vague questions such as “does this look good?” become background noise. Five concrete comparisons—version, SHA, filenames, hashes, and notes—make the final review repeatable.

3. Verify the tag against the commit you actually reviewed

Tags are the release’s identity anchor. Before publishing, record the commit SHA that passed review and compare it to the tag target. Do this from a clean checkout or CI workspace rather than a developer machine with uncommitted changes and multiple remotes.

git fetch --tags origin
git rev-parse HEAD
git rev-list -n 1 v2.4.1
git show --no-patch --decorate v2.4.1

The first two SHA-producing commands should agree only if HEAD is the commit intended for release. The third command reports the commit reached by the tag. The final command gives a human-readable check of the tag and commit decoration.

For a release created by automation, make the expected SHA an input to the workflow rather than an implication. For example, a release job triggered after merging a release pull request can receive the merge commit SHA, build from that SHA, tag that SHA, and record it in the job output. This prevents a later push to the branch from silently becoming the release source.

Once an immutable release is published, GitHub does not permit moving or deleting its tag while the release exists. That means “we will repoint v2.4.1 after lunch” is not a recovery plan. Catching an incorrect SHA before publication is materially different from noticing it after users have copied the download URL.

4. Make checksums describe this build, not a similarly named build

A checksum file is only useful when it was generated from the exact files attached to the release. Generate it in the same artifact directory, after packaging is complete and before assets are uploaded.

cd dist
sha256sum mytool_2.4.1_linux_amd64.tar.gz \
          mytool_2.4.1_linux_arm64.tar.gz \
          mytool_2.4.1_darwin_arm64.tar.gz \
          > checksums.txt

On systems where sha256sum is unavailable, the equivalent command may be shasum -a 256 FILE. The important control is not the command name; it is that the generated checksum list contains every release artifact and no artifact from a prior directory.

Before upload, inspect the list itself. A release containing three archives and a checksums.txt file with only two lines should fail the gate. Also download one staged artifact and verify it with the checksum file when your process has a staging location available:

sha256sum --check checksums.txt

Do not upload a corrected checksum file later as a workaround. Immutable GitHub releases do not permit adding, replacing, or deleting assets after publication. If the checksum is wrong, users cannot reliably determine which released file is authentic; the remediation is a corrected successor release, not a silent hash revision.

5. Use this hotfix decision tree after publication

When someone reports a release problem, first classify the defect. The decision is not “can we edit the release?” but “would an edit change what a downloader receives or which source commit they believe they received?” If yes, preserve the bad release as evidence and issue a new release identity.

What is wrong? Can immutable-release settings allow a direct correction? Recovery action
Typo or missing explanation in release notes Yes. GitHub permits editing the title and release notes. Edit the notes, state what changed, and retain the release version.
Release incorrectly marked pre-release or latest Yes. GitHub permits changing pre-release and latest status. Correct the status and verify links from the Releases page.
Wrong binary, archive, signature, SBOM, or checksum No. Assets cannot be added, replaced, or deleted after publication. Publish a corrected new version with a new tag and explain the supersession.
Tag points to the wrong commit No. The associated tag cannot be moved or deleted while the release exists. Create a new corrected version and tag at the intended commit.
Release version itself is invalid or misleading Not safely, if correcting it would alter artifacts or tag meaning. Publish a successor version; use the old notes to direct users away from it.

This is the second-order benefit of immutability: the incident record remains inspectable. Users can see exactly which bad artifact existed, while maintainers publish a clearly distinct fix instead of rewriting history beneath a familiar URL.

6. Recover from a bad uploaded asset with a successor release

Suppose v2.4.0 contains a file named mytool_2.4.0_linux_amd64.tar.gz, but its contents were packaged from an ARM64 build. The filename is plausible, the upload succeeds, and the failure appears only when an AMD64 user runs it.

Do not try to solve this by changing the file locally, regenerating checksums.txt, or asking users to “download the same URL again.” Under immutable releases, GitHub blocks asset replacement. More importantly, reusing the same version would leave package managers, mirrors, and users with incompatible expectations about what 2.4.0 means.

  1. Stop promoting the affected version in documentation, install snippets, and announcements.
  2. Identify every affected asset and the exact scope: for example, only Linux AMD64 rather than all platforms.
  3. Build the corrected artifacts from the intended reviewed commit.
  4. Generate a new checksum file from those corrected artifacts.
  5. Create the next project-appropriate version and a new tag at the verified commit.
  6. Publish notes stating that the prior version contains an incorrect asset and naming the corrected version.

If your project uses semantic versioning, an artifact-only correction will commonly be released as the next patch version, such as v2.4.1. The critical rule is consistency with your project’s published version policy, not the specific digits chosen during an incident.

7. Recover from a tag that points at the wrong commit

A tag-target incident is more serious than a release-note typo because it changes the source provenance users expect to inspect. Imagine v3.0.0 was tagged from a release branch before the final security fix merged. The release assets may even be internally consistent with that earlier commit, but they do not contain the code you intended to ship.

With mutable tags, teams historically fixed this by force-moving v3.0.0. That approach creates a split reality: one user may have cloned the old tag, while another receives the new target from the same name. Immutable releases deliberately rule out that ambiguity after publication.

The decision path is straightforward:

  • If the release is not published, delete or recreate the local and remote tag according to your repository policy, then rerun the gate.
  • If the immutable release is published, do not attempt to reuse the tag name as the corrected identity.
  • Create a corrected release version from the intended commit, verify its SHA, and publish it as the supported release.
  • Edit the original release notes to make the supersession visible, since GitHub allows release-note edits.

Include both commit identifiers in the advisory when possible: the incorrect tag target and the corrected tag target. That gives downstream maintainers a deterministic way to identify which source they fetched.

8. Write correction notes for users, not for internal postmortems

GitHub allows title and release-note edits even when immutable releases are enabled. Use that remaining flexibility for user safety, not cosmetic cleanup. A vague note such as “updated release” hides the exact information an automated consumer or security reviewer needs.

For an asset or tag problem, add a short notice near the top of the original release notes:

Do not use this release for new installations. The published Linux AMD64 archive is incorrect. Use v2.4.1 instead. Existing users of the Linux AMD64 archive should replace it with the v2.4.1 artifact and verify its checksum.

Then make the successor release notes equally explicit. State whether the fix changes only packaging, changes source code, or changes both. A packaging-only correction should not be described as a security fix unless the underlying issue actually has security impact.

Keep the operational timeline outside the release notes if it is long. Your incident document can cover detection, CI investigation, and preventive actions. Release notes should answer three user questions in under a minute: Am I affected? What should I download? How can I verify the replacement?

9. Put the playbook into your release workflow this week

You do not need a new platform to make immutable releases workable. Start by turning the five release-boundary items into a pull request template or an issue form used for every planned publication.

  1. Add fields for planned version, tag name, expected commit SHA, asset list, and checksum filename.
  2. Require one reviewer to compare the tag target with the reviewed commit before publication.
  3. Have CI generate checksums from the final artifact directory rather than copying a stored checksum file.
  4. Create two release-note snippets in advance: one for a superseded asset and one for a wrong tag target.
  5. Decide who can declare a release unsupported and who publishes the replacement version.

Finally, run a 15-minute tabletop exercise using a harmless hypothetical: “v1.8.0 contains the wrong Windows archive.” Ask the team to identify the next version, the new tag, the checksum verification step, and the exact user notice. If the answers require a meeting, the process is not ready for a real hotfix.

Immutable releases make supply-chain rewriting harder because published assets and tags cannot be quietly changed. The tradeoff is that release engineering must be disciplined before publication and unambiguous after it. A rehearsed successor-release path turns that tradeoff from a stressful dead end into a predictable operational procedure.