GitAnnotated Tags

Annotated Tags

Annotated tags are the recommended way to mark releases and important milestones. Unlike lightweight tags, which are nothing more than a named pointer, an annotated tag is a full Git object stored in the object database. It carries a rich set of metadata: who created it, when, and a descriptive message. Annotated tags can also be cryptographically signed with GPG, giving consumers of your software a way to verify authenticity.

What an annotated tag stores
  • Tagger name — the user.name from your Git config at the time the tag was created.

  • Tagger email — the user.email from your Git config.

  • Creation date — a full ISO timestamp of when the tag was made (separate from the commit date).

  • Tag message — a freeform description, just like a commit message. Can be multiline.

  • Object reference — the SHA of the commit (or tree or blob) the tag points to.

  • Optional GPG signature — a cryptographic signature that proves the tag was created by the holder of a specific private key.

Creating an annotated tag (inline message)

The most common way to create an annotated tag is with the -a flag and -m to supply the message inline, without opening an editor.

Create an annotated tag on the current commit

Bash
# Verify you are on the right commit first
git log --oneline -3
# 7e4f1a2 (HEAD -> main) Final tweaks before release
# 5c3d2b1 Add release notes to README
# b1c2d3e Update version in package.json

# Create the annotated tag
git tag -a v1.0.0 -m "Release 1.0.0"

# With a more detailed message:
git tag -a v1.0.0 -m "Release 1.0.0

This is the first stable release of the project.
- Complete authentication system
- Dashboard with live charts
- REST API v1 finalized
- Supports Node.js 18+"

# Confirm tag was created
git tag
# v1.0.0
Interactive creation (opens editor)

If you omit the -m flag, Git opens your configured text editor — exactly like git commit — so you can write a longer, formatted message. This is especially useful for detailed release notes.

Open editor to write the tag message

Bash
git tag -a v1.0.0
# (your $GIT_EDITOR / $EDITOR opens with a template)

# The editor shows:
#
# Release 1.0.0
#
# # Write a message for tag:
# #   v1.0.0
# # Lines starting with '#' will be ignored.

# Save and close — the tag is created with your message.
Viewing annotated tag details with git show

The git show command is the most informative way to inspect an annotated tag. It prints the tag object metadata first, then the commit it points to, then the diff.

Full git show output for an annotated tag

Bash
git show v1.0.0

# tag v1.0.0
# Tagger: Alice Smith <alice@example.com>
# Date:   Thu Nov 16 14:30:00 2023 +0000
#
# Release 1.0.0
#
# This is the first stable release of the project.
# - Complete authentication system
# - Dashboard with live charts
# - REST API v1 finalized
# - Supports Node.js 18+
#
# commit 7e4f1a2c3b4d5e6f7a8b9c0d1e2f3a4b (HEAD -> main, tag: v1.0.0)
# Author: Alice Smith <alice@example.com>
# Date:   Thu Nov 16 13:55:00 2023 +0000
#
#     Final tweaks before release
#
# diff --git a/src/version.ts b/src/version.ts
# index ...
Tag object SHA vs commit SHA
Notice that the tag object has its own SHA (visible with `git rev-parse v1.0.0`) that is different from the commit SHA (`git rev-parse v1.0.0^`). The `^` suffix dereferences a tag object to the underlying commit.

Dereferencing the tag to get the commit SHA

Bash
# SHA of the tag object itself
git rev-parse v1.0.0
# a4b9c3e2f1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6

# SHA of the commit the tag points to
git rev-parse v1.0.0^{}
# 7e4f1a2c3b4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f

# These two values are different — that is what makes the
# tag "annotated" (it has its own object in the database).
Pushing an annotated tag to a remote

Git does not push tags automatically when you run git push. You must push them explicitly. This is true for both lightweight and annotated tags.

Pushing a single annotated tag

Bash
# Push one specific tag
git push origin v1.0.0
# Enumerating objects: 1, done.
# Counting objects: 100% (1/1), done.
# Writing objects: 100% (1/1), 175 bytes | 175.00 KiB/s, done.
# Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
# To github.com:alice/myproject.git
#  * [new tag]         v1.0.0 -> v1.0.0

Push all local tags at once

Bash
git push origin --tags
# (pushes every tag that the remote does not already have)

# Push only annotated tags (skip lightweight):
git push origin --follow-tags
# (safer: pushes annotated tags reachable from HEAD)
Why annotated tags are preferred for releases

Reason

Explanation

Authorship

You know who created the tag and when — critical for audit trails in professional projects.

Message

The tag message can describe what changed, acting as a concise changelog entry.

Tool support

GitHub, GitLab, npm, and most CI/CD tools recognize annotated tags as "real" releases.

git describe output

git describe uses annotated tags by default, producing clean version strings like v1.0.0-3-gabc1234.

GPG signing

Only annotated tags can be signed, enabling cryptographic release verification.

Object permanence

An annotated tag is a first-class object; it cannot be confused with a branch or a commit.

GPG signing an annotated tag

For high-security projects — especially open-source libraries with many consumers — you can add a GPG signature to an annotated tag. This lets anyone with your public key verify that the tag was really created by you and has not been tampered with.

Create a GPG-signed annotated tag

Bash
# Prerequisite: you have a GPG key configured
gpg --list-secret-keys --keyid-format LONG
# sec   rsa4096/ABC123DEF456 2022-01-01 [SC]

# Tell Git which key to use (one-time setup)
git config --global user.signingkey ABC123DEF456

# Create a signed tag with -s instead of -a
git tag -s v1.0.0 -m "Release 1.0.0 (signed)"

# Verify the signature
git tag -v v1.0.0
# object 7e4f1a2c...
# type commit
# tag v1.0.0
# tagger Alice <alice@example.com> 1700000000 +0000
#
# Release 1.0.0 (signed)
# gpg: Signature made Thu 16 Nov 2023 14:30:00 UTC
# gpg:                using RSA key ABC123DEF456
# gpg: Good signature from "Alice Smith <alice@example.com>" [ultimate]
GPG signing requires your private key to be available
If you are running in a CI/CD pipeline or on a shared server, you need to import your GPG private key into the environment before creating signed tags. Never commit or expose your private key — use a secrets manager or hardware security key.
Creating an annotated tag on a past commit

Tag a commit from history with a message

Bash
# Find the commit hash you want to tag
git log --oneline --decorate
# 7e4f1a2 (HEAD -> main) Post-release cleanup
# 5c3d2b1 Final tweaks before v1.0.0   ← this one
# b1c2d3e Update README

# Create the annotated tag on that specific commit
git tag -a v1.0.0 5c3d2b1 -m "Release 1.0.0 (applied retroactively)"
Common errors

Error: empty tag message

Bash
# If you open the editor and save without writing anything:
# error: no tag message?
# (tag creation is aborted)

# Fix: always provide a non-empty message

Error: tag already exists

Bash
git tag -a v1.0.0 -m "..."
# fatal: tag 'v1.0.0' already exists

# Fix: delete the existing tag and recreate it
git tag -d v1.0.0
git tag -a v1.0.0 -m "Release 1.0.0"

# Or force-overwrite (dangerous if already pushed to remote):
git tag -a -f v1.0.0 -m "Release 1.0.0"
Annotated tags as lightweight changelogs
Write your annotated tag messages as mini changelogs. Tools like GitHub automatically use the tag message as the body of the release notes when you publish a release, so a well-written message saves you time in the release process.