Git Release Tags: Check the Commit Behind an Existing Tag
Check an existing Git tag without moving it. Compare local and remote object IDs, recognise missing or mismatched tags, and separate signatures from build provenance.
Two people can have a tag called v1.2.0 and still be looking at different Git objects. To check an existing release tag, compare its local tag-object ID, its resolved commit ID and the exact reference advertised by the intended remote. Do this before fetching replacements or moving any tags: a mismatch is evidence to investigate, not a command to overwrite it.
The examples use Bash or Git Bash, including the backslash line continuations. The main checks below do not switch your checkout, create a tag or push anything. They use the example tag v1.2.0 and remote origin; substitute your actual release name and repository. If you are preparing a new release instead, the separate guide to creating and sharing an annotated release tag starts from an approved commit.
Establish what exists locally
Check the full tag reference, rather than an abbreviated name that could also identify a branch:
git show-ref --verify refs/tags/v1.2.0 On success, this prints an object ID and the full reference name. If it fails, do not assume the release is missing everywhere: this check concerns your local repository. The show-ref reference documents exact-name verification.
When the reference exists, inspect its object type and resolve its commit:
git cat-file -t refs/tags/v1.2.0
git rev-parse --verify refs/tags/v1.2.0
git rev-parse --verify 'refs/tags/v1.2.0^{commit}' Keep the two full IDs, in order:
- Object type tag: the unpeeled ID identifies an annotated tag object. The final command follows the tag to its commit, so its ID is different.
- Object type commit: this is a lightweight tag directly naming a commit. Both IDs are the same; there is no separate annotation object.
- The commit-resolution command fails: the reference did not resolve to an available commit. A tag can name another object type, so do not label every tag target a release commit.
The object-type command distinguishes these cases. The revision syntax reference explains peeling: ^{commit} follows tags until it finds a commit, or fails if it cannot.
For an annotated commit tag, inspect the annotation and referenced commit metadata without checking out its files:
git show --no-patch refs/tags/v1.2.0 The tagger and message provide context, but they are not authenticated simply because Git displays them. Compare the resolved commit ID with an independent release review or build record. Comparing it only with your current HEAD answers a different question: your branch may have moved since the release.
Ask the intended remote for the exact name
First confirm which repository origin identifies:
git remote get-url --all origin A remote name is local configuration, not proof that you are querying the project's authoritative release repository. If you are checking a previous push, inspect git remote get-url --push --all origin too: the remote manual describes the distinction. Resolve a wrong project or unexpected destination before comparing hashes.
Then request only the full tag name and its peeled form:
git ls-remote --exit-code --tags origin \
'refs/tags/v1.2.0' 'refs/tags/v1.2.0^{}' Keep the quotes. Do not replace the patterns with v1.2.0*: that also admits names such as v1.2.0-rc1 and v1.2.01. This query reads the remote's advertised references; it does not fetch the tag into your repository.
For an ordinary annotated tag pointing to a commit, expect two lines. Compare like with like:
refs/tags/v1.2.0: compare its ID with the local unpeeled tag-object ID.refs/tags/v1.2.0^{}: compare its ID with the local resolved commit ID.
A lightweight tag normally produces only the first line, whose ID is the directly referenced object. A remote peeled line alone does not prove its target is a commit; your local ^{commit} check establishes that type when the matching object is available. Do not add --refs to this diagnostic query: it suppresses peeled lines. Git's remote listing reference documents the output.
With --exit-code, exit status 2 means no matching reference was returned. Authentication, transport and other errors are different failures. In Bash, echo $? immediately after the command shows its status. A failed connection says nothing about whether the tag exists. Even a successful missing-name result describes what that endpoint advertised to your account at that time, not every repository or inaccessible reference.
Interpret the mismatch before changing anything
Both IDs match
Your local annotated tag and that remote's advertised tag agree at the time of the query. You have not yet established that this is the approved release: compare the commit with the release record and check any required signature separately.
The commit matches but the tag-object ID differs
The source commit can be the same while the annotation objects differ. For example, recreating an annotation with a different message produces a different tag object even when it still names the same commit. Our Git 2.52.0 fixture reproduced this case.
Do not dismiss the difference as harmless just because the source agrees. A release policy may require a particular annotation or signature. Keep both object IDs and ask the release owner which object was approved. If one side is lightweight and the other annotated, compare their commit targets but also record that difference in form.
The commit IDs differ
The shared name identifies different source history. Record the repository URL, full tag name, both object IDs and both commit IDs. Check for a wrong remote, wrong version or a tag that was recreated. The hashes establish disagreement; they do not tell you who changed what or which side is correct.
Do not force-fetch or force-push to make the numbers match. That can erase the local symptom without settling which release consumers received. Keep the evidence and coordinate a correction with the maintainers.
It exists remotely but not locally
The absence of a local tag is not a reason to recreate it. You can first report the remote IDs without changing local state. If you need the actual object for inspection, and the local name is confirmed absent, this optional step imports that one tag:
git fetch --no-tags --no-prune origin \
refs/tags/v1.2.0:refs/tags/v1.2.0 Unlike the earlier checks, this writes local objects and a tag reference, plus fetch bookkeeping. It does not switch your working files or publish anything. --no-tags disables automatic following of other tags; the explicit refspec still requests this one. --no-prune avoids pruning as part of this check. These controls are documented in the fetch manual.
Stop on a rejection instead of forcing it, then rerun the local type and ID checks. If a conflicting local tag already exists, leave it intact and use a separate disposable repository for any deeper inspection.
It exists locally but is not advertised remotely
That establishes a local label and an absent advertised remote name, not a completed release. It could reflect an unpublished tag, the wrong remote, a removed tag or access restrictions. Check the project's release record. Do not push a local label merely to make it appear remotely.
Check signatures without confusing them with provenance
An annotated tag can be unsigned. If the project expects a signed tag, run its verification process; Git's command for the tag object is:
git verify-tag refs/tags/v1.2.0 The verification reference covers this check. Read the result and diagnostic together. An unsigned tag, a missing key, an untrusted signer or an invalid signature are not interchangeable conclusions. In our unsigned fixture, verification failed with no signature found; that was expected, not evidence of a damaged commit.
A successful cryptographic check still needs the project's trusted signer policy. Do not accept a newly supplied key solely because it arrived with the release you are checking. For SSH signatures, Git explicitly distinguishes valid signatures from trusted ones through its allowed signers configuration.
Finally, matching a tag and trusting its signer do not prove that a particular binary was built from that source. Check the build record, artefact identity and any provenance verification required by the project separately. Your useful conclusion is specific: which tag object you inspected, which commit it resolves to, whether the intended remote agrees, and which trust or build checks remain unresolved.
Checks exercised on 29 October 2026 with Git 2.52.0 on Linux in disposable repositories and a local bare remote, including matching, missing, conflicting, lightweight and non-commit tags. Real signing identities, hosted access policies and build provenance were not tested.
Subscribe
Get new articles by email. Unsubscribe with one click.