A release can be green, tagged v2.4.0, and listed on GitHub—yet still fail for the customer who needs the exact Linux binary three months later. That failure usually happens when a team assumes its Git tag, GitHub release, and CI artifact are three names for the same thing.
They are related outputs, but they answer different questions. A tag answers “which source commit?” A GitHub release answers “which version should users know about and download?” A CI artifact answers “which files should this workflow or its operators pass between jobs, inspect, or retain temporarily?”
The practical consequence is simple: do not decide that “the release exists” until all three outputs have an owner, a consumer, and a retention rule. A pipeline that creates only one of them is often complete for a pull request, but incomplete for a software release.
Map the three outputs before writing release automation
The fastest way to remove ambiguity is to treat a release as three separate records with explicit contracts. GitHub’s own interface separates Releases and Tags, while GitHub Actions artifacts are workflow outputs that can persist beyond an individual workflow run. That separation is a clue: GitHub does not treat these as interchangeable objects.
| Output | Primary purpose | Typical consumer | Retention decision | What it must identify |
|---|---|---|---|---|
| Git tag | Immutable-looking source reference for a version | Developers, source builders, deployment automation | Keep for the supported lifetime of the version; protect release tags from casual deletion or movement | A specific commit, such as the commit shipped as v2.4.0 |
| GitHub release | Public or internal release record with notes and downloadable release assets | Users, support engineers, package consumers, release managers | Keep while users or support teams may need the version | The tag, release status, notes, and intended downloadable files |
| CI artifact | Workflow handoff, test evidence, build inspection, and temporary binary storage | Later CI jobs, QA, engineers debugging a run | Expire according to a deliberate short- or medium-term policy | The workflow run, job, platform, and build inputs that produced it |
The key rule is: choose storage based on the consumer’s time horizon. A compiler job needing a ZIP file in the next job has a CI-artifact need. A customer needing an installer six months later has a release-asset need. A developer rebuilding an old version needs a tag that points at the correct source commit.
Use one concrete release path from commit to customer
Consider a command-line tool called acme-cli. The team has merged fixes, CI passed on commit 8f3c1ab, and it intends to ship version v2.4.0 for macOS, Linux, and Windows. The pipeline must make three different handoff decisions.
- Create an annotated Git tag,
v2.4.0, pointing to8f3c1ab. This is the source identity for the release. - Build the three platform archives from that tagged commit and upload them as CI artifacts. This makes the files available to validation or publishing jobs.
- Run smoke tests against the downloaded artifacts, not merely against files sitting in the build workspace.
- Create a GitHub release associated with
v2.4.0, attach the validated archives, and publish notes that name compatibility changes and upgrade steps. - Retain the CI artifacts only as long as they are needed for investigation or rerunning operational steps; retain the GitHub release assets for the period users are expected to download that version.
If the Linux archive disappears when artifact retention ends, the published release is unaffected because its customer-facing copy is attached to the GitHub release. If someone asks which source made the Windows archive, the release points to v2.4.0, and the tag points to the commit. Each output covers a failure mode the others do not.
Git tags are source identity, not a release announcement
A release tag should identify exactly one source state. For a conventional versioned release, use an annotated tag rather than relying on an informal branch name or a mutable label such as production. Annotated tags carry a message and tagger metadata; they are the appropriate form when the tag is itself a release record.
git checkout 8f3c1ab
git tag -a v2.4.0 -m "Release v2.4.0"
git push origin v2.4.0
git show v2.4.0
The final command is not ceremony. It verifies the tag’s target and exposes the source state a future maintainer will inspect. Run it before publishing a release page or deploying production.
- Use one version syntax consistently, for example
v2.4.0rather than alternating between2.4.0andrelease-2.4. - Tag a commit that has passed the required release checks, not “whatever is currently on
main.” - Record the commit SHA in the release workflow logs or release notes when support teams commonly diagnose source-level regressions.
- Make tag creation an explicit authorization point if a pushed tag triggers deployment.
A tag is not a user interface. It has no structured statement that the version is stable, no curated upgrade instructions, and no guarantee that a user will find the binary they need. Conversely, a release page without a trustworthy tag loses the clean source-to-binary trail needed to rebuild or audit it.
GitHub releases are the support and distribution contract
A GitHub release turns a tag into a version people can evaluate and consume. GitHub lets maintainers choose a tag when drafting a release, mark a release as a pre-release, and manage which release is presented as the latest. Those controls are product decisions, not merely metadata fields.
For v2.4.0, the release page should answer four questions without sending a user to a CI run: what changed, whether it is production-ready, what files to download, and which source version those files came from. “Latest” should mean the version you recommend to ordinary users, not simply the most recently created object.
Minimum release-page checklist
- Associate the release with the existing release tag, such as
v2.4.0. - Use a title that matches the version users will report in issues:
acme-cli v2.4.0. - State whether it is stable or a pre-release. Do not rely on a paragraph saying “probably not for production.”
- Include notable changes, breaking changes, migration steps, and any platform-specific caveat.
- Attach the validated customer-facing binaries or link users to the canonical distribution channel.
- Set it as latest only when it is the recommended stable release.
This is why a repository can have many tags and few releases. Tags may mark internal milestones, experiments, or source versions; a release is a distribution and communication commitment. The Git repository itself illustrates the distinction: it has tags but no GitHub releases, because those are separate choices rather than automatic equivalents.
CI artifacts are workflow handoffs, not your permanent download site
GitHub Actions artifacts are built for moving data between jobs and preserving workflow outputs after a run. They can hold test reports, coverage data, binaries, and core dumps. That makes them valuable for release work, but their workflow-centered nature is precisely why they should not silently become the only place a customer can obtain a shipped binary.
For the acme-cli example, name artifacts so an operator can identify both the version and platform without opening an archive:
acme-cli-v2.4.0-linux-amd64
acme-cli-v2.4.0-macos-arm64
acme-cli-v2.4.0-windows-amd64
test-results-v2.4.0
Separate distributable files from diagnostic evidence. A 40 MB Windows archive belongs in the release publishing path. A coverage report, test log, or core dump belongs in a CI artifact with a retention period suited to engineering investigation. Combining them into one generic artifact called build makes handoff slower and accidental publication more likely.
Artifact retention must be a written policy, not an assumption that “GitHub keeps it.” For example, a team can retain ordinary pull-request test evidence for 14 days, release-candidate artifacts for 30 days, and publish final customer binaries to GitHub releases. Those are team policy choices, not universal platform defaults.
Make the provenance chain inspectable
The release handoff is trustworthy only if someone can trace a downloaded file backward. For every published binary, the chain should be: release asset → GitHub release → Git tag → commit → successful build and validation run. A missing link turns a routine support question into forensic work.
Add a small machine-readable manifest to the artifact and, where useful, attach it to the release. The format can be plain text; the value is that it captures the facts that disappear from a chat thread.
version=v2.4.0
git_tag=v2.4.0
git_commit=8f3c1ab
artifact=acme-cli-v2.4.0-linux-amd64.tar.gz
platform=linux-amd64
workflow_run=release
The exact run identifier is less important than consistent linkage. If a support engineer receives acme-cli-v2.4.0-linux-amd64.tar.gz, they should be able to establish whether it was built from the tagged commit rather than from a later commit on main.
Do not solve this only with filenames. A filename can be copied or renamed. The tag and release association are the durable human-facing references, while the CI run and artifact record provide the operational evidence.
Prevent the four substitute-one-for-another failures
Most release process bugs are category errors. The pipeline technically produces an output, but it produces the wrong output for the person who needs it next.
| Bad shortcut | What breaks | Corrective rule |
|---|---|---|
| “The tag is enough.” | Users have no curated notes, stability signal, or verified binary download path. | Create a GitHub release when the version is meant to be consumed as a supported distribution. |
| “The CI artifact is the download.” | Users depend on workflow-oriented storage and whatever retention policy happens to apply. | Promote validated customer binaries to release assets or the designated package registry. |
| “The GitHub release is enough.” | A binary’s source commit is unclear or difficult to rebuild. | Associate every release with a specific, protected release tag. |
| “Build it again when publishing.” | The published bytes may differ from the bytes that passed validation. | Build once, validate the stored artifact, then publish that validated output. |
The last failure is the one teams miss under time pressure. If tests validate one build and a later publishing step recompiles from a branch, then “same version” does not necessarily mean “same bytes.” Treat the validated artifact as the promotion candidate, while using the tag to prove its source identity.
Assign ownership at each release gate
A checklist without named ownership becomes a release manager’s memory test. Assign a responsible role even on a two-person project. The developer who merges code, the workflow that compiles it, and the person who decides it is safe for users may be different actors.
| Gate | Owner | Evidence required |
|---|---|---|
| Version source selected | Release owner | Commit SHA and intended version |
| Tag created | Authorized maintainer or release workflow | git show v2.4.0 points to the approved commit |
| Build completed | CI workflow | Named artifacts for every supported platform |
| Artifacts validated | CI workflow or QA owner | Smoke-test result against downloaded artifact files |
| Release published | Release owner or automation | Release associated with tag, notes, assets, and correct stable/pre-release status |
This assignment also clarifies environment protection. If a production deployment triggers on tag creation, tag creation is not a harmless source-control action; it is a deployment authorization boundary. Design the trigger and repository permissions accordingly.
Run this release handoff checklist this week
Pick the next non-emergency release and run this list before changing any automation. It takes less time than reconstructing which build a customer downloaded after artifacts have expired.
- Open your repository’s Releases page and Tags page. Identify one recent tag that has no release and one release whose downloadable binaries exist only in CI.
- Write a one-line retention policy for each output: tags, release assets, and CI artifacts. Include an actual duration for CI artifacts, such as 14 or 30 days.
- Choose one version format, such as
vMAJOR.MINOR.PATCH, and create annotated tags for production releases. - Change your release workflow so it builds from the tagged commit, uploads named artifacts, and tests those uploaded files.
- Publish the tested files as GitHub release assets rather than asking users to locate an Actions run.
- Add a manifest containing the tag and commit SHA to every distributable archive.
- Review who can create tags that trigger deployment, and separate draft, pre-release, and stable-release decisions.
The goal is not to create more release objects for their own sake. It is to make each object carry the responsibility it is suited for: the tag preserves source identity, the GitHub release serves users, and the CI artifact carries work safely through the pipeline.