GitHub Release vs Tag: What's the Difference?
Open the Go repository on GitHub and its Releases page says "There aren't any releases here". Yet git ls-remote on the same repository lists go1.27.3, go1.26.10 and hundreds of other version tags. Go publishes tags only, and that is the whole GitHub release vs tag difference in practice: a tag is a name Git gives to a commit, a release is a record GitHub builds on top of a tag, and GitHub's own release notifications and latest-release API only read releases. If you follow Go that way, you hear nothing.
Short answer: a Git tag is a named pointer to a commit that lives in the repository and moves with git push and git fetch. A GitHub release is a GitHub record attached to one tag that adds notes, downloadable files and the draft, pre-release and Latest flags. Every published release has a tag, a tag doesn't need a release, and GitHub's release notifications and latest-release API only see releases.
1. A Tag Is a Git Ref, a Release Is a GitHub Record
Git has never heard of a GitHub release. A tag is a ref under refs/tags/, stored in the repository next to your branches, and a normal clone fetches it. A release lives in GitHub's own database. Cloning the repository gives you the tags and none of the release titles, notes or uploaded binaries.
GitHub's documentation says that releases are based on Git tags, and a tag date may differ from a release date because the two can be created at different times.
Lightweight and Annotated Tags
Git itself has two kinds of tag. A lightweight tag is only a name for a commit. An annotated tag is a separate object with a tagger, a date, a message and an optional signature. The git-tag manual says: "Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels." You can see the difference in a throwaway repository (Git 2.43):
$ git tag v1.0.0-light
$ git tag -a v1.0.0 -m "Release 1.0.0"
$ git cat-file -t v1.0.0-light
commit
$ git cat-file -t v1.0.0
tag
$ git rev-parse v1.0.0 v1.0.0^{commit}
3c28401a3875e819546fcc12f869a37237d9a49f # the tag object
e89e24ceaa0c7676a1dec6d9ee4e9e8d38bb41d5 # the commit it points toThat second SHA matters if you pin anything by hash. An annotated tag has its own object ID, different from the commit ID. In git ls-remote --tags output the commit shows up on an extra line ending in ^{}. For the Linux kernel's v6.1 tag, the tag object is 7614896 and the commit is 830b3c6. The Go repository's tags returned no ^{} lines when we listed them on November 5, 2026, so they are lightweight: the tag name and the commit share one SHA.
What a Release Adds on Top of the Tag
A release takes one tag and attaches things Git has no place for. Per GitHub's docs, that means a title and Markdown notes (written by hand or generated from merged pull requests), up to 1,000 uploaded assets of under 2 GiB each, and source code zip and tarball links that GitHub adds automatically. It also carries state: a release can be a draft, a pre-release, or the one marked Latest.
| Git tag | GitHub release | |
|---|---|---|
| Stored in | The Git repository | GitHub, outside Git |
| Created with | git tag and a push | Web UI, gh release create, REST API |
| Carries | A commit (plus message and signature if annotated) | Title, notes, assets, draft / pre-release / Latest |
| Can be changed later | Yes, Git lets you move or delete it | Yes; immutable releases lock the tag and assets |
| REST endpoint | /repos/OWNER/REPO/tags | /repos/OWNER/REPO/releases |
2. Every Published Release Has a Tag, Not Every Tag Has a Release
A release can create its own tag, and deleting a release does not delete the tag.
Creating a Release Can Create the Tag
If you name a tag that doesn't exist yet, GitHub creates it for you. The gh release create manual says the tag is created "from the latest state of the default branch", and the REST API does the same with target_commitish, which defaults to the default branch and is ignored when the tag already exists. A typo in the version number therefore gives you a release on whatever the default branch points at right now, not on the commit you tagged locally. The CLI has a guard for that (syntax from the gh manual):
git tag -a v2.4.0 -m "v2.4.0" git push origin v2.4.0 # abort if v2.4.0 is not on the remote; use the tag message as notes gh release create v2.4.0 --verify-tag --notes-from-tag
Deleting a Release Leaves the Tag Behind
The reverse is also asymmetric. gh release delete removes the release and keeps the tag unless you pass --cleanup-tag. Delete the newest release and leave its tag, and you get a repository where the tag list says v3.1.0 while the Latest release says v3.0.2. Anyone who reads only one of the two sees a different "current version".
Projects That Only Push Tags
Some large projects publish versions only as tags. Both golang/go and torvalds/linux showed an empty Releases page when we checked on November 5, 2026, and they publish their versions as tags. You can still read those tags without the GitHub API. This ran against the Go repository on the same day:
$ git ls-remote --tags --refs https://github.com/golang/go \
| awk '{print $2}' | sed 's#refs/tags/##' \
| grep -E '^go1\.[0-9]+\.[0-9]+$' | sort -V | tail -3
go1.27.1
go1.27.2
go1.27.3The --refs flag drops the peeled ^{} lines, and the grep keeps only stable versions, so release candidates such as go1.25rc3 fall out. The naming scheme is Go's own; for another project you have to write a different filter, which is the practical cost of tracking tags instead of releases.
3. GitHub Release vs Tag for Notifications and APIs
- Watch, Custom, Releases on a repository lets you choose releases as the only event you hear about, and GitHub describes that as being notified when new releases are published. A pushed tag is not a published release, so on golang/go that setting has nothing to report.
GET /repos/OWNER/REPO/releases/latestreturns the most recent release that is neither a draft nor a pre-release, sorted bycreated_at, which GitHub defines as the date of the release commit, not the date the release was drafted or published. When a release is published, the REST API setsmake_latestto true by default, while gh release create decides from date and version. Maintainers can turn it off for a backport. The docs only describe thecreated_atsort, but when we compared the endpoint with the Latest label on 131 repositories on October 4, 2026, it returned the labeled release every time, including 8 repositories whose newest release was a backport without the label.GET /repos/OWNER/REPO/tagsreturns a name, a commit SHA and archive URLs per tag, with no date field. To know which tag is newest you parse the names or fetch each commit.- GitHub also serves an Atom feed of tags. On November 5, 2026 the feed at github.com/golang/go/tags.atom listed go1.27.3 at the top. For tags-only projects this is the lowest-effort way to follow along, and our guide to GitHub release RSS feeds covers readers and aggregation.
What GitWatchman Watches
GitWatchman tracks releases, not tags. Every 2 hours it checks each repository's Latest release, and the release it announces always comes from the REST endpoint above, so drafts and pre-releases never trigger an alert (if you want release candidates, see tracking GitHub pre-releases). It sends a notification when the tag name of that release differs from the one it stored last time. The core of the decision, from supabase/functions/check-releases/check.ts:
const tagChanged = repo.last_release !== release.tag_name;
if (tagChanged && isRollback(repo, release)) {
// The newest release was deleted and GitHub fell back to an older
// one the followers were already notified about: store it so the
// UI matches GitHub (and the GraphQL pre-check converges), notify
// nobody.
// ...
}
const isNewRelease = tagChanged;That rollback branch exists because of the deletion case from section 2: when the newest release disappears, the Latest endpoint falls back to the previous one, and without the check every follower would be told about a version they already had.
GitWatchman does not watch bare tags. It refuses to add a repository that has no releases, so golang/go can't be tracked at all. And because the comparison is by tag name, a tag force-moved to a new commit under the same name produces no notification either. For those projects, the tags feed or a scheduled git ls-remote is the better fit.
5. What to Publish if You Maintain a Project
If people depend on your code, publish both: an annotated tag for Git, and a release on top of it for everyone who reads GitHub. With a tag alone, everyone relying on GitHub's Releases watch setting, the Latest badge or a release tracker won't hear about it.
- Create annotated tags (
git tag -a) and push them withgit push --follow-tags, which per the git-push manual sends annotated tags that point at commits you are pushing and leaves lightweight ones behind. - Name tags by version so tools can sort them. The semantic versioning guide covers what goes in the major, minor and patch numbers.
- Create the release from the existing tag with
--verify-tag, and mark release candidates as pre-releases so they don't take the Latest badge. - When you ship a fix for an older line after a newer one, publish it with
make_latest=false(or--latest=falsein gh) unless you mean it to become the version people install by default. - If you attach binaries, turn on immutable releases and publish from a draft.
Our guide on writing GitHub release notes goes through structure and the auto-generated option.
Frequently Asked Questions
Is a GitHub release the same as a Git tag?
No. A Git tag is a ref inside the repository that names one commit, and it travels with git fetch and git push. A GitHub release is a record stored by GitHub that points at one tag and adds a title, notes, downloadable assets and the draft, pre-release and Latest flags. Every published release has a tag; a tag can exist without any release.
Does deleting a GitHub release delete the tag?
Not by default. The GitHub CLI command gh release delete removes only the release unless you add --cleanup-tag, which also deletes the tag. If the release was immutable, the tag can be deleted once the release is gone, but that tag name can never be reused.
Can I get notified about new tags instead of releases?
Yes, but not through GitHub's Releases watch setting or the latest-release API, which only see releases. For a project that only pushes tags, subscribe to its tags.atom feed (for example github.com/golang/go/tags.atom) in a feed reader, or poll git ls-remote --tags from a scheduled job and compare the output with the previous run.
Which release does GitHub mark as Latest?
Usually the newest full release. The REST endpoint for the latest release returns the most recent release that is neither a draft nor a pre-release, ordered by created_at (the date of the release commit), and in practice it returns the release carrying the Latest label. When publishing, make_latest defaults to true in the REST API, while gh release create decides automatically from date and version. Both let a maintainer opt out, for example for a backport.
Get an alert for every new release
GitWatchman checks the Latest release of the public GitHub repositories you follow every 2 hours and alerts you by email, Slack, Discord, Telegram or a personal RSS feed. Free during the beta, up to 5 repositories.
Start tracking releases