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.
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.
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
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.
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:
- Declare the affected version and exact tag SHA.
- Preserve release page, asset digest, incident evidence, and deployment scope.
- Stop new promotion/download automation from selecting the bad version; do not silently mutate it.
- Publish a corrected version after normal review/evidence.
- Update release notes or support documentation to warn against the bad version; if you change the “latest” designation, record why.
- Rollback deployed systems to a previously verified version or forward-fix according to service risk.
- 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
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?
It makes the same version name resolve to different source identities for different consumers and destroys reproducibility/audit evidence. Publish a corrected version instead.
What should differ between the v0.9.0 and v1.0.0 Release records?
At minimum their tags/commit identities and prerelease state; v0.9.0 is prerelease while v1.0.0 is a full stable release and may be latest.
Why hash the downloaded asset again instead of trusting its filename?
A filename is only a label. The digest proves whether the downloaded bytes match the recorded artifact evidence.
If the checkpoint used immutable releases, when should assets be uploaded?
While the Release is still a draft. After publication, immutable release assets cannot be modified/deleted.
A release automation downloads releases/latest in
production. What risk does that introduce?
The input moves when latest changes. Pinning an exact version/tag or digest gives more predictable promotion and rollback behavior.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.