Chapter 11Lesson 05~190 minutes

Checkpoint Lab — Tags, Releases, Release Notes, Assets, Changelogs, and Release Governance

Run an end-to-end release rehearsal with a prerelease and stable release, exact commit identities, hashed assets, generated notes, a simulated bad release, and an auditable rollback/yank policy.

Checkpoint labPrereleaseStable releaseYank policy

Learning objectives

  • Create two exact tagged versions in a disposable repository.
  • Publish v0.9.0 as a prerelease and v1.0.0 as a full stable release.
  • Attach and independently SHA-256 hash harmless assets.
  • Use generated release notes and inspect the exact comparison/tag/commit identities.
  • Inject and diagnose a bad release decision without rewriting consumed history.
  • Write a rollback/yank policy that preserves auditability and finish with a complete cleanup/verification checklist.
Checkpoint assumptions: GitHub.com + GitHub Free, a disposable public personal repository you own, GitHub CLI authenticated, Git installed, and ability to run Git Bash/Bash/zsh or PowerShell equivalents. Immutable releases remain disabled because this lab intentionally deletes its disposable releases/tags. No production package, secret, signing key, or real customer artifact is used.

1. Scenario and operating objective

You maintain release-governance-checkpoint. The team wants a v0.9.0 release candidate, then a v1.0.0 stable release. Every version must map to an exact commit, each uploaded artifact must have a recorded SHA-256, release notes must be inspectable, and a bad-release decision must be handled without moving a consumed tag.

Prediction A: publishing v0.9.0 as a prerelease creates a GitHub Release object but does not change main or the tag target. Prediction B: publishing v1.0.0 later creates a separate tag/Release; it does not mutate v0.9.0. Record both predictions before proceeding.

2. Preflight: prove identity, permissions, and clean starting state

gh auth status --active --hostname github.com

gh repo create release-governance-checkpoint --public --clone --add-readme
cd release-governance-checkpoint

gh repo view --json nameWithOwner,visibility,defaultBranchRef,viewerPermission
git status --short --branch
git remote -v
gh release list --json tagName,isDraft,isPrerelease,isLatest,isImmutable
git tag --list

Stop if you are not in the disposable repository or if unexpected releases/tags already exist. Cleanup commands later assume this exact lab boundary.

3. Build the v0.9.0 candidate through a PR and tag the merged commit

git switch -c feature/rc
printf "release candidate
" > app.txt
git add app.txt
git commit -m "Add release candidate"
git push -u origin feature/rc

gh pr create --base main --head feature/rc \
  --title "Prepare v0.9.0 release candidate" \
  --body "Checkpoint candidate for generated release notes."

gh pr merge --squash --delete-branch
git switch main
git pull --ff-only

RC_SHA=$(git rev-parse HEAD)
git tag -a v0.9.0 "$RC_SHA" -m "v0.9.0 release candidate"
git push origin refs/tags/v0.9.0

# Independent identity proof
printf 'RC_SHA=%s\n' "$RC_SHA"
git rev-list -n 1 v0.9.0
git ls-remote origin refs/tags/v0.9.0

All three observed SHAs should identify the intended release-candidate commit (with annotated-tag nuance: ls-remote may show the tag object at the plain ref; the peeled ^{{}} entry identifies the commit). Use git ls-remote origin 'refs/tags/v0.9.0*' if you want both entries.

4. Create a prerelease draft, attach/hash an asset, then publish

mkdir -p dist
printf "candidate artifact v0.9.0
" > dist/app-v0.9.0.txt
sha256sum dist/app-v0.9.0.txt

gh release create v0.9.0 dist/app-v0.9.0.txt \
  --draft --verify-tag --generate-notes \
  --title "v0.9.0 — release candidate"

# Inspect before publication.
gh release view v0.9.0 --json tagName,isDraft,isPrerelease,isImmutable,targetCommitish,assets

# Mark prerelease and publish the assembled draft.
gh release edit v0.9.0 --prerelease --draft=false

gh release view v0.9.0 --json tagName,isDraft,isPrerelease,isImmutable,publishedAt,assets,url
PowerShell hash: Get-FileHash .\dist\app-v0.9.0.txt -Algorithm SHA256. Store the observed digest in your lab evidence.

5. Produce v1.0.0 as a second, independent version

git switch -c feature/stable
printf "stable release
" >> app.txt
git add app.txt
git commit -m "Promote stable release"
git push -u origin feature/stable

gh pr create --base main --head feature/stable \
  --title "Prepare v1.0.0 stable release" \
  --body "Stable checkpoint change after v0.9.0."
gh pr merge --squash --delete-branch

git switch main
git pull --ff-only
STABLE_SHA=$(git rev-parse HEAD)
git tag -a v1.0.0 "$STABLE_SHA" -m "v1.0.0 stable"
git push origin refs/tags/v1.0.0

printf "stable artifact v1.0.0
" > dist/app-v1.0.0.txt
sha256sum dist/app-v1.0.0.txt

gh release create v1.0.0 dist/app-v1.0.0.txt \
  --draft --verify-tag --generate-notes \
  --notes-start-tag v0.9.0 \
  --title "v1.0.0"
gh release edit v1.0.0 --draft=false

gh release list --json tagName,name,isDraft,isPrerelease,isLatest,isImmutable,publishedAt

v0.9.0 should remain a prerelease. v1.0.0 is a full published release and may be the current latest release. The two tags should resolve to different recorded commit identities.

6. Verify commit and artifact identity independently

# Git identities
printf 'v0.9.0 -> '; git rev-list -n 1 v0.9.0
printf 'v1.0.0 -> '; git rev-list -n 1 v1.0.0

# Hosted Release metadata
gh release view v0.9.0 --json tagName,isPrerelease,isImmutable,assets
gh release view v1.0.0 --json tagName,isPrerelease,isImmutable,assets

# Download and verify stable uploaded bytes
mkdir -p verify-download
gh release download v1.0.0 -p 'app-v1.0.0.txt' -D verify-download
sha256sum verify-download/app-v1.0.0.txt

# Versioned REST evidence
gh api -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  repos/{owner}/{repo}/releases/tags/v1.0.0 \
  --jq '{tag_name,target_commitish,draft,prerelease,immutable,assets:[.assets[]|{name,size,digest}]}' 

Do not treat target_commitish alone as the final tag SHA when an existing tag is used; verify the Git tag itself with Git. The Release field describes hosted release targeting metadata, while the Git ref is the source identity.

7. Inject a bad release decision without corrupting history

Simulate discovering that v1.0.0 has a serious operational defect after publication. The deliberately bad proposal is: “replace app-v1.0.0.txt and move v1.0.0 to a fixed commit.” Do not execute it.

Rejected recovery: do not force-move v1.0.0 and do not use gh release upload --clobber to substitute different bytes under the same consumed version. Those actions erase the stable meaning of existing evidence.

Instead, create a hypothetical v1.0.1 corrective release after the fix is reviewed. For this lab, document the policy rather than creating a third release.

8. Write the rollback/yank runbook

GitHub Releases do not provide a universal package-manager-style “yank” semantic for every consumer. Define your own operational policy. A useful minimum is:

  1. Declare the affected version and exact tag SHA.
  2. Preserve release page, asset digest, incident evidence, and deployment scope.
  3. Stop new promotion/download automation from selecting the bad version; do not silently mutate it.
  4. Publish a corrected version after normal review/evidence.
  5. Update release notes or support documentation to warn against the bad version; if you change the “latest” designation, record why.
  6. Rollback deployed systems to a previously verified version or forward-fix according to service risk.
  7. Only delete a published release/tag if your project policy explicitly requires removal and the audit/provenance consequences are understood.

For immutable production releases, the platform itself prevents tag/asset replacement, which makes “new corrective version” the natural path.

9. Production release operating policy produced by the lab

Control Checkpoint policy
Version identity Tag an exact reviewed commit; use --verify-tag
Preparation Draft first; finalize notes and assets before publish
Artifacts Record SHA-256; prefer build provenance/attestation in production
Notes Generated change inventory + human operational review
Prerelease Explicit non-production qualification state
Stable/latest Full release only after qualification; automation should pin exact version
Correction New version; never redefine consumed tag/asset bytes
Publisher access Write access only to release operators; admin only for controls such as immutability
Production hardening Enable immutable releases and verify release/asset attestations

10. Cleanup and rollback of the disposable lab

Destructive cleanup: only after saving your lab observations, and only in release-governance-checkpoint. The commands intentionally remove the disposable releases and tags.
# Delete hosted Release objects first.
gh release delete v1.0.0 --yes
gh release delete v0.9.0 --yes

# Prove tags remain, then delete only these two disposable tags.
git ls-remote --tags origin 'refs/tags/v*'
git push origin :refs/tags/v1.0.0
git push origin :refs/tags/v0.9.0
git tag -d v1.0.0 v0.9.0

# Archive the disposable repository rather than broadening auth for deletion.
gh repo archive --yes

# Final read-only proof.
gh release list
git tag --list

Archiving is the chapter’s final reversible repository cleanup. Permanent repository deletion is optional and should only be performed from GitHub settings if you intentionally want it and understand the consequences.

11. Verification checklist

  • v0.9.0 and v1.0.0 were created from explicit annotated tags.
  • Each tag’s peeled commit identity was recorded independently.
  • The prerelease and stable Release states were observed through structured CLI/API output.
  • Each uploaded asset had an independently computed SHA-256.
  • Generated notes used an explicit start tag for v1.0.0.
  • The bad-release scenario was corrected by policy, not tag movement or same-version asset replacement.
  • Cleanup demonstrated Release/tag independence and removed only disposable resources.

12. What this chapter adds to the production GitHub operating model

You now have a release boundary that joins repository governance to distribution: exact commit → explicit tag → reviewed Release metadata → immutable artifact identity → notes/support policy → rollback/yank decision. Chapter 12 moves from this controlled release workflow to the GitHub CLI itself as an automation interface: authentication context, scripting, machine-readable output, error handling, and safe repository operations.

Knowledge check

After v1.0.0 is published, a defect is found. Why is moving the v1.0.0 tag a poor recovery?

What should differ between the v0.9.0 and v1.0.0 Release records?

Why hash the downloaded asset again instead of trusting its filename?

If the checkpoint used immutable releases, when should assets be uploaded?

A release automation downloads releases/latest in production. What risk does that introduce?

Next lesson

Next: Chapter 12 — GitHub CLI, gh Authentication, Repository Operations, and Scripting Workflows

Further reading — current official GitHub sources

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.