At 09:12, your CI pipeline can create the Git tag v1.2.3; at 14:40, a release manager can publish the GitHub release for that same tag after signing and uploading artifacts. Treating those as one event is how teams accidentally deploy an unreviewed tag to production or announce a package before its checksums exist.
The reliable model is simple but operationally important: a tag identifies source code, while a GitHub release is a publication record built around that tag. Once a team uses those as separate gates, semantic versions become useful inputs to CI, package publishing, deployment approval, release notes, and customer-facing announcements rather than just labels on commits.
1. Start with one concrete version: v1.2.3
Assume the main branch contains a bug fix that should become version 1.2.3. Under semantic versioning, that version normally communicates a backward-compatible bug-fix release after 1.2.2. The precise meaning of “backward compatible” is your project’s contract, but the version should be decided before release automation starts.
At minimum, this is the Git object relationship you want:
main
|
o---o---o commit 8f3c2ab
^
|
v1.2.3
The tag v1.2.3 points at commit 8f3c2ab. Anyone can check out that exact source with git checkout v1.2.3, compare it with the prior version using git diff v1.2.2..v1.2.3, and reproduce a build from the tagged commit.
A GitHub release is not another commit and is not a replacement for that reference. It is GitHub metadata attached to the versioned tag: a title, release notes, publication state, optional release assets, and options such as prerelease status and the latest-release label. That distinction is the foundation for every workflow decision that follows.
2. A Git tag is a source reference, not a launch announcement
Git tags mark a specific point in repository history. For a release workflow, create an annotated tag rather than relying on an informal branch name or a commit message that says “release.” An annotated tag carries a message and tagger information, making it more useful during audits and when reviewing a repository months later.
git checkout main
git pull --ff-only origin main
git log -1 --oneline
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3
Before pushing, verify that the commit is the commit you intend to ship. A tag is difficult to reason about if it points to a developer’s local merge commit instead of the reviewed commit on main. The command git show v1.2.3 should display the expected commit and the tag annotation.
Use a consistent format such as v1.2.3, including the v prefix if that is what your existing tooling and package conventions expect. The important rule is consistency: do not publish 1.2.2, then tag v1.2.3, then configure a deployment workflow to match only release-*. Version parsing failures usually arrive at the worst possible time: during a rollback.
3. A GitHub release is the publication record around that tag
After v1.2.3 exists, create a GitHub release for it. GitHub releases are based on Git tags, but they add the information downstream users actually need: what changed, whether the build is ready for production, and where to obtain packaged artifacts.
| Question | Git tag | GitHub release |
|---|---|---|
| What does it identify? | A specific point in Git history | A published version built around a tag |
| Can a user check out source from it? | Yes, directly | Indirectly, through its selected tag |
| Can it include release notes? | Only a short tag annotation | Yes, with a title and full release notes |
| Can it hold binary files and checksums? | No | Yes, as release assets |
| Should it trigger production announcement? | Usually no | Usually yes, when published |
For example, the v1.2.3 release might include a changelog link, a signed archive, a SHA256SUMS file, and a concise note describing one fixed regression. The tag says, “this is the source.” The release says, “this is the package and communication event we intend users to consume.”
4. Why the tag date and release date legitimately differ
GitHub explicitly notes that a tag date can differ from a release date because the tag and the release can be created at different times. That is not a data-quality problem. It often records a healthy separation between selecting code and publishing software.
Consider this realistic sequence for v1.2.3:
- At 09:12 UTC, CI tags commit
8f3c2abafter tests pass. - At 09:20 UTC, a build system produces the macOS, Linux, and Windows artifacts from that tag.
- At 11:00 UTC, a maintainer verifies checksums and reads generated release notes.
- At 14:40 UTC, the maintainer publishes the GitHub release.
The tag records when source was selected. The release date records when the project made its release record public. If a security or compliance review asks, “When did users receive the fixed binary?”, the release publication time is usually the more relevant answer. If an engineer asks, “Which commit did the build originate from?”, the tag is the authoritative answer.
The tradeoff people skip is that a gap between tag and release creates a window in which automation can act too early. Do not make “tag pushed” mean “deploy to production” unless your tag creation itself is the approved production gate.
5. Use tags for immutable build identity and releases for approval
A dependable pipeline assigns one responsibility to each event. A tag push starts reproducible work: build, test, generate a software bill of materials if your process uses one, calculate checksums, and upload candidate artifacts somewhere controlled. A published release starts public-facing work: documentation updates, customer notifications, production deployment, or package registry promotion.
That produces a clean decision rule:
- Use a tag event when the job must know exactly which source revision to build.
- Use a release publication event when the job must wait for a human or controlled workflow to declare the version ready.
- Use neither event alone for credentials, signing, or production access; require the repository and environment protections appropriate to your team.
In GitHub Actions, these are separate triggers. A workflow reacting to a tag push can build candidate artifacts, while another workflow reacting to a published release can promote already-verified output.
on:
push:
tags:
- "v*"
For the publication side, use the release event rather than trying to infer publication from a tag name. This avoids a common failure mode: a contributor pushes v1.2.3, and an unrestricted workflow immediately publishes or deploys before release notes, assets, or approval are complete.
6. Put prereleases before stable versions, not beside them
A prerelease is for a version that users may test but should not treat as production-ready. For the 1.2.3 line, use a semantic version such as v1.2.3-rc.1 for a release candidate, then create a GitHub release and select This is a pre-release.
The suffix matters. v1.2.3-rc.1 and v1.2.3 are not two names for the same artifact; they are two distinct version identifiers. Build and publish them separately. If you discover a packaging issue in the candidate, publish v1.2.3-rc.2 rather than moving the old tag to a new commit.
A useful release-candidate path looks like this:
- Tag and build
v1.2.3-rc.1. - Create a GitHub prerelease with test instructions and known limitations.
- Collect validation feedback against that exact version.
- Tag the approved final commit as
v1.2.3. - Publish a non-prerelease GitHub release for
v1.2.3.
Do not turn a prerelease into your stable release merely by changing prose in the notes. The stable version deserves its own tag, release record, artifacts, and checksums because consumers should be able to distinguish the tested candidate from the final published version.
7. Treat “Latest” as a product-routing decision
GitHub can automatically assign the latest-release label based on semantic versioning when you do not explicitly select Set as latest release. You can also select that option deliberately. The practical point is that “Latest” is not a synonym for “most recently created tag.” It is a routing label for users who arrive at the repository and want the default version.
For a normal stable release, allow semantic versioning to do the expected work: publish v1.2.3 as a non-prerelease, and it should supersede v1.2.2 as the stable line advances. For a release candidate such as v1.2.3-rc.1, mark it as a prerelease and do not use it to steer ordinary users toward unstable software.
| Version being published | Mark as prerelease? | Set as latest? | Reason |
|---|---|---|---|
v1.2.3-rc.1 |
Yes | No | It is intended for testing, not general production use. |
v1.2.3 |
No | Usually automatic by SemVer | It is the stable patch release. |
v2.0.0 |
No | Usually automatic by SemVer | It becomes the default stable major version. |
Override the label only when you have a clear support-policy reason. A project maintaining two major lines may deliberately keep an older supported line prominent for a period, but that is a conscious product decision, not something a CI job should guess.
8. Avoid the mutable-tag trap after v1.2.3 ships
The worst release incident is not a typo in release notes. It is a tag that silently changes which source it identifies after users, CI systems, or package builders have consumed it. If v1.2.3 points to a different commit tomorrow, a user rebuilding “the same version” may get different source.
If the wrong commit was tagged, prefer a new version such as v1.2.4 after fixing the issue. If the error happened before publication and your team must replace the tag, stop every downstream job first and document the correction. After publication, a new release is easier to audit than a rewritten version history.
GitHub also offers immutable releases for repositories that need stronger protection. The important consequence is that immutability affects both the release record and the tag that underpins it: once published under that protection, teams cannot rely on editing assets or moving the tag later. That constraint is valuable for supply-chain integrity, but it forces discipline earlier in the process.
Before publishing, verify four things: the tag resolves to the expected commit, artifacts were built from that tag, checksums match those artifacts, and release notes describe the shipped version. Those four checks cost minutes; a post-publication correction can cost every downstream user a rebuild and every maintainer an explanation.
9. Implement this workflow in your repository this week
You do not need a release platform migration to make this reliable. Start by writing down which event means “build candidate” and which means “publicly ship.” Then test the workflow with a patch version such as v1.2.3 in a repository where you can inspect every resulting event.
- Choose one tag format, such as
vMAJOR.MINOR.PATCH, and document it inCONTRIBUTING.md. - Require release tags to point to reviewed commits on
main. - Configure a tag-triggered workflow to build and verify artifacts without automatically announcing or deploying them.
- Create the GitHub release only after assets and release notes are ready.
- Mark
-alpha,-beta, and-rcversions as prereleases. - Let stable semantic versions receive the latest-release label unless a documented support policy says otherwise.
- Record a no-retag rule: once a stable release is published, fix mistakes with a new version.
The result is a workflow with two useful timestamps, two deliberate gates, and one unambiguous source reference. When someone asks whether v1.2.3 is merely built, approved, publicly available, or safe to deploy, your tags, releases, and automation will each give the same answer.