A README that links to /releases/latest can silently send a customer from the version they tested last month to a newer binary they have never approved. The URL still works, which makes this failure harder to catch than a broken link.

The underlying mistake is treating a Git tag, a GitHub Release, and a download URL as interchangeable names for “a version.” They are related, but they solve different jobs. A tag gives Git and automation a named commit; a GitHub Release gives humans a version page with notes and attached files; a URL either intentionally follows the newest release or permanently identifies one release.

Start with the distinction users can actually see

On GitHub, open a repository and click Releases near the file list. The resulting area has two views: Releases and Tags. That split is the most useful mental model for deciding what to publish.

The Tags view is a chronological list of Git references. A tag such as v2.4.0 points at a particular commit. It is valuable even when no release page exists: a maintainer can check out the source, compare versions, trigger a pipeline, or identify the exact revision used in production.

The Releases view contains GitHub’s publishing layer. A release is associated with a tag, but it can also include release notes, a title, a pre-release status, and files intended for users. GitHub itself makes the distinction concrete: the git/git repository has tags but its Releases page says there are no releases.

That is not an incomplete repository. It is a project choosing tags as its versioning interface. The right question is therefore not “should every tag become a release?” It is “does anyone need a human-facing version page or downloadable packaged output?”

Use this decision table before creating anything

Choose the smallest publication mechanism that meets the consumer’s need. Creating a release for every internal build creates noise; publishing only tags for a desktop tool forces users to assemble source archives or hunt through CI logs.

Project situation Publish What the tag does What users should receive
An internal service deployed only by CI An annotated Git tag is usually enough Marks the commit deployed as v1.8.3 A commit, tag, or deployment record; not necessarily a GitHub Release link
A library distributed through a package registry Tag; add a Release when release notes need a durable public home Connects registry version, source, and CI provenance A version-specific release URL for notes, plus the registry package URL for installation
A CLI, desktop app, plugin, or self-hosted tool with binaries A GitHub Release Identifies the source revision used to build assets The version-specific release page or a direct asset link
A release candidate or beta build A GitHub pre-release associated with a tag Preserves the exact candidate revision The explicit pre-release URL, never a generic “latest stable” instruction
A source-only open source project Tag only, unless curated notes are useful Provides a checkout and comparison point The tag URL for source consumers; a release URL only if it adds notes or packaging value

The overlooked row is the package-registry library. A package manager is normally the installation endpoint, not GitHub. For example, an npm package should document its package name and version range; a GitHub Release can be the readable changelog page linked from a release announcement or support ticket.

Know the three URLs that mean different things

For a repository at https://github.com/acme/widget, these links communicate different promises:

URL Meaning Best use Main risk
/releases/latest Take the reader to the repository’s latest release An “upgrade to the current release” button The destination changes after the next release
/releases/tag/v2.4.0 Take the reader to one named GitHub Release Changelog entries, incident reports, versioned docs It requires a GitHub Release for that tag
/tags Show the repository’s tag list Source-oriented workflows and project history It does not provide curated release notes or assets

GitHub documents releases/latest as the link for the latest release and gives every created release a unique URL. Treat “latest” as a moving channel and the tag release URL as an immutable citation. This one rule prevents most documentation mistakes.

Do not put /releases/latest in a changelog line reading “Fixed in v2.4.0.” Six months later, it may resolve to v2.7.0, making the historical record misleading. Link that sentence to /releases/tag/v2.4.0 instead.

Work through a CLI release from commit to download

Suppose Acme publishes a cross-platform command-line tool named Widget. A contributor merges a fix for a credential parsing bug. CI passes on commit 8f3c..., and the team decides it is version v2.4.0.

  1. Create an annotated tag on the validated commit: git tag -a v2.4.0 -m "Widget v2.4.0".
  2. Push it: git push origin v2.4.0.
  3. Build the macOS, Linux, and Windows binaries from that tag, not from the moving default branch.
  4. In GitHub, choose Releases, select Draft a new release, and select v2.4.0.
  5. Write notes that name the bug fix and any upgrade action, then attach the produced files.
  6. Publish the release and use its unique release URL in the changelog.

The tag is the technical anchor. If a binary is questioned later, the team knows which source revision it was meant to represent. The Release is the distribution record: it says what changed and gives a user-visible place to retrieve the files.

If Widget’s website says “Download the newest Widget,” it can link to https://github.com/acme/widget/releases/latest. If a support reply says “Download the build that fixes the parsing issue,” it should link to https://github.com/acme/widget/releases/tag/v2.4.0.

A changelog is historical documentation. Each entry should retain its meaning when read two years later, after 20 more versions exist. That makes a unique release URL the default target for a version heading, release announcement, pull request summary, or customer-facing fix notice.

A compact Markdown-style changelog rendered in a repository might contain language like this:

## v2.4.0
Fixed credential parsing for quoted values.
Full notes: https://github.com/acme/widget/releases/tag/v2.4.0

The Release page is preferable to a tag page when the reader needs more than source identity. It can carry migration notes, known limitations, acknowledgements, and attached assets. A tag page is preferable when the reader needs to inspect the exact source point and the project has deliberately not created a release.

There is a maintenance benefit too: one canonical unique URL reduces duplicate prose. Instead of copying a 12-line upgrade warning into a website, issue comment, and newsletter, publish it in the release notes and point each channel at that release. Correcting a factual error then has one obvious place to fix.

“Download” is not one use case. A person installing Widget for the first time usually wants the current supported build. A build engineer reproducing a customer environment usually needs exactly v2.4.0. Those needs deserve different links.

  • Current download page: use /releases/latest when the product policy is “use the newest release.”
  • Versioned documentation: use /releases/tag/v2.4.0 so the page and its downloads remain tied to the documented version.
  • Bug report or rollback instruction: use the unique release URL, because “latest” may already contain unrelated changes.
  • Direct binary download: link to a specific release asset only when your install script or documentation requires one named file.

The tradeoff nobody mentions is that a direct asset URL couples your documentation to a filename. Rename widget-linux-amd64.tar.gz to widget_2.4.0_linux_amd64.tar.gz, and every copied direct link can fail. A version-specific release page is more resilient for humans because it exposes the available assets even if naming conventions evolve.

For scripts, be stricter: choose a version explicitly rather than allowing a production deployment to fetch whatever happens to be newest. “Latest” is a product-navigation concept, not a reproducibility guarantee.

Do not let pre-releases leak into stable instructions

GitHub allows a release to be marked as a pre-release, which is useful for v2.5.0-rc.1, beta builds, and early compatibility testing. This is a separate publication decision from creating the tag. The tag still gives testers an exact commit; the pre-release communicates that the build is not ready for normal production use.

Use an explicit pre-release link in test instructions. For example, send testers to the release page for v2.5.0-rc.1, tell them the supported platforms, and ask them to report results against that version. Do not tell them to use a generic download page and assume they will select the intended candidate.

When you publish the stable version, create or publish the stable Release associated with the stable tag and make the stable channel unambiguous. GitHub’s release form also offers a “Set as latest release” option. Use that control deliberately when your project’s public “latest” link is part of installation documentation.

The operational consequence is simple: beta testers can move quickly without changing what new production users receive. Mixing those audiences behind one moving URL creates support tickets that look like user error but are really release-channel ambiguity.

Keep tags and releases separate in automation

A tag push and a GitHub Release publication are distinct events, so your automation should reflect the approval boundary you actually want. A common workflow is: a maintainer pushes a tag, CI builds and tests from that tag, then a release is drafted or published after the artifacts are available.

This separation matters when not every developer should be able to publish customer-facing downloads. Tag-based automation can build candidate artifacts, while release publication remains a reviewed step. Conversely, a team that wants fully automated releases can have a pipeline create the release after the tagged build succeeds.

Use consistent names across all three layers:

  • Git tag: v2.4.0
  • GitHub Release title: Widget v2.4.0
  • Package version where applicable: 2.4.0
  • Changelog heading: v2.4.0

Consistency is not cosmetic. It lets a support engineer search one version string and move from a package report to a tag, release notes, and source comparison without guessing whether 2.4, release-2.4.0, and v2.4.0 mean the same build.

Write a four-line policy in the repository’s maintainer documentation, then apply it to the next release. The policy should specify not only how to create a version, but also which URL belongs in each communication channel.

  1. Use annotated tags such as v2.4.0 for every version that must be reproducible.
  2. Create a GitHub Release only when users need curated notes, packaged downloads, or a public version page.
  3. Use /releases/tag/{tag} in changelogs, documentation tied to a version, support replies, and rollback instructions.
  4. Use /releases/latest only in places intentionally meant to track the current release, such as a primary download button.
  5. Use the Tags view for source history and Git workflows; do not imply that it provides the same installation experience as a Release.

Finally, audit three existing links: the README download button, the newest changelog entry, and one support article. If all three point to /releases/latest, change the latter two to version-specific release URLs. That small cleanup gives users a stable record of what changed while preserving a convenient path to the newest build.