Release Automation, Tags, GitHub Releases, Packages, and Container Registries: Diagnostics, Failure Modes, and Production Practices
Diagnose release failures without rebuilding evidence, broadening credentials, moving trusted tags, or deleting the first-failure record.
Learning objectives
- Diagnose release failure from source/ref selection through artifact bytes, permissions, registry state and consumer verification.
- Preserve original run/attempt, tag/release/package IDs and digests before retries or cleanup.
- Recognize dangerous shortcuts such as republishing different bytes under one version or broadening token scope.
- Separate GitHub workflow success from external registry/package and deployment state.
- Apply the least destructive correction at the causal layer.
1. Evidence-first diagnostic sequence
- Preserve run ID/attempt, first failure logs, event/ref/SHA and workflow revision.
- Confirm version/tag target and whether that name already exists.
- Confirm exact built subject digest and whether promotion changed bytes.
- Confirm evaluated job permissions and which code runs with release/package authority.
- Inspect artifact transfer, attestation result and release/package API response.
- Inspect external registry/package state by immutable ID/digest rather than alias alone.
- Apply the smallest correction, then rerun only the equivalent scope while preserving the original record.
2. Failure: building from a mutable or wrong source ref
# Broken release idea — DO NOT USE
on:
workflow_dispatch:
jobs:
release:
steps:
- run: git clone https://github.com/example/app && cd app && git checkout main
- run: ./build-and-publish.sh
This workflow does not prove which commit was built. The branch can move after dispatch, and the clone is detached from the event-selected repository revision. Repair by selecting and recording the exact source SHA, checking out that SHA, then carrying the digest of the resulting artifact across publication.
3. Failure: same version, different bytes
Suppose version 1.4.2 already exists with digest A, but
a retry rebuilds and produces digest B. Overwriting the registry or
release asset destroys the meaning of the version. The correct
investigation asks why bytes changed—toolchain, dependency,
timestamp, source, build flags—and either reuse digest A or publish
a new version after validation.
sha256sum dist/package.tgz
gh release view v1.4.2 --json assets,tagName,targetCommitish
# For OCI, inspect the immutable digest instead of only the tag:
docker buildx imagetools inspect ghcr.io/example/app:v1.4.2
4. Failure: token is broader than the failing operation
A 403 while creating a Release is not evidence that the workflow
needs write-all. Confirm the API endpoint and required
permission. GitHub Release/tag mutation belongs to
contents: write; GHCR/package publication belongs to
packages: write. A third-party registry may require a
different credential, but that credential should be scoped to one
disposable namespace and never printed.
Do not troubleshoot an authentication failure by dumping
github context, printing tokens, disabling
protections, or switching to a broad personal token. Inspect
response status, permission configuration and target ownership
instead.
5. Failure: attesting before final bytes exist
If a workflow creates an attestation for
package.zip and then modifies, recompresses or signs
the file, the published digest no longer matches the attested
subject. Preserve the original digest/attestation as first-failure
evidence, finalize the bytes, compute the final digest, then create
a new attestation for the exact published subject.
6. Failure: publishing latest from pull requests
PR code is untrusted validation input, not release authorization. A
workflow that gives PR jobs packages: write and pushes
latest lets ordinary test activity mutate
consumer-facing state. Keep PR validation read-only; publish only
from a separately guarded tag/manual/release path after exact source
verification.
7. Failure: deleting evidence before diagnosis
A failed release may have created a tag but no release, a draft release but no assets, one asset but no package, or a package digest but no deployment. Deleting everything immediately removes clues. Record each existing object ID and digest first, then clean only the partial disposable resources that are safe to remove.
8. Causal layer map
| Symptom | Likely layer | Inspect before changing |
|---|---|---|
| Workflow never starts | event/filter/ref selection | tag push, workflow presence at selected revision, event payload |
| Checkout SHA mismatch | source/configuration |
GITHUB_SHA, checkout output/HEAD, tag target
|
| Build bytes drift | toolchain/dependency/build | versions, lock inputs, timestamps, file digest |
| 403 release API | token/permissions |
job permissions, endpoint, repository access
|
| Push succeeds, deploy pulls old image | registry/tag resolution | registry manifest digest and deployment-resolved digest |
| Attestation verify fails | subject/identity | published file digest, attestation subject and signer identity |
| Green release job, package missing | external system/state | package/registry API result; job success is not package existence |
9. Intentionally broken example: hidden failure behind mutable alias
# Broken operational check
image='ghcr.io/example/app:latest'
docker pull "$image"
echo 'release verified' # proves only that something called latest was pullable
Interpretation: the check never compares the pulled manifest digest with the digest the release workflow recorded. Repair by storing the expected digest as release evidence, resolving the registry reference, and comparing exact digests before deployment or promotion.
10. Least-destructive recovery patterns
- Wrong tag target: stop before publication; create a new correct unique tag rather than force-moving a released immutable tag.
- Wrong asset bytes: preserve digest; fix build; publish a new version or replace only a still-draft disposable release before public consumption.
- Partial prerelease: inspect release/tag/assets first, then delete the exact lab objects if policy allows.
- Bad production version: redeploy a known-good existing digest and publish a superseding fix; do not rewrite history.
- Credential leak: revoke/rotate the credential at its provider, then investigate published state; deleting logs alone is not remediation.
11. Lesson summary
Release debugging is identity debugging plus side-effect accounting. Preserve the exact source, artifact digest and external object IDs, then repair the layer that diverged.
Knowledge check
A release retry produces a different SHA-256 for the same version. What should happen?
Stop publication and investigate why bytes changed. Do not silently overwrite the existing version.
A GHCR push returns 403. Is contents: write the
obvious fix?
No. GHCR publication normally needs package authorization such
as packages: write plus correct package/repository
access.
Why is an attestation created before final packaging insufficient?
Packaging changes the bytes and digest, so the attestation no longer covers the published subject.
A job is green but the registry tag points to an old digest. Which layer failed?
External registry/publication verification, not job execution.
Why preserve a partial release before cleanup?
Its tag target, release ID, asset IDs/API errors and digests are causal evidence for what succeeded before failure.
Official references and version notes
- About releases — GitHub Releases are based on Git tags and add release metadata/assets around a versioned source point.
- Managing releases — Current release creation, editing and deletion behavior.
- Immutable releases — Current tag/asset immutability and automatic release-attestation behavior.
- GHCR — Current GitHub Container Registry authentication, package linking and digest guidance.
- GitHub Packages permissions — Repository/granular package permissions and GitHub Actions access.
- GITHUB_TOKEN authentication — Least-privilege token use in workflows.
- gh release create — Current CLI release creation, --verify-tag and immutable-release handling.
- gh release verify — Verification of cryptographically signed immutable-release attestations.
- gh release verify-asset — Verify a local asset against the release attestation and digest.
- actions/attest v4.2.2 — Immutable attestation action revision referenced by optional provenance examples.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.