Create and Verify a Tag Object with git mktag

Most people run git tag -a and never think about the object underneath it. git mktag lets you build and validate that tag object by hand, which matters when you are debugging a signing workflow or a tool that generates tags itself. This guide uses the installed git-mktag from Git 2.43.0, packaged as git-man 1:2.43.0-1ubuntu7.3. Allow about 15 minutes for a first test, including creating a disposable repository.

Checkpoint: This command creates an object in the current repository's object database. It does not create or move a human-readable tag name such as v1.0.0. If you only need an ordinary annotated tag, git tag -a is usually the simpler interface. Use git mktag when you need to validate and store the tag object's exact contents.

1. Check the installed command

Run this as your normal user. No elevated privileges are required for a repository you own:

$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man git
git-man 1:2.43.0-1ubuntu7.3
git 1:2.43.0-1ubuntu7.3

The manual's synopsis has no positional options: git mktag reads the complete tag from standard input and prints one object identifier on standard output. Option parsing still accepts --strict and --no-strict, covered below.

2. Prepare a disposable repository and target commit

Use a temporary repository while learning the format. This creates one commit and stores its ID in a shell variable, touching only the directory named by mktemp:

$ workdir=$(mktemp -d /tmp/git-mktag-guide.XXXXXX)
$ git -C "$workdir" init
$ git -C "$workdir" config user.name 'Guide Test'
$ git -C "$workdir" config user.email [email protected]
$ printf 'sample\n' > "$workdir/file.txt"
$ git -C "$workdir" add file.txt
$ git -C "$workdir" commit -m 'Create test commit'
$ commit=$(git -C "$workdir" rev-parse HEAD)
$ printf '%s\n' "$commit"
6f...  # your commit ID will differ

Git prints its normal commit summary. That shortened ID above is a shape example, not a value to paste. Keep the directory around until every check passes; there is no cleanup command here, since deleting a path before checking it is an avoidable way to lose your test data.

Checkpoint: Confirm the target is really a commit before constructing the tag:

$ git -C "$workdir" cat-file -t "$commit"
commit

3. Build the four required tag headers

A tag record names an existing object, gives its Git type, supplies the tag name, and identifies the tagger. A blank line separates those headers from the optional message. The tagger value carries a name, email address, Unix timestamp, and numeric timezone.

$ cat > "$workdir/tag.txt" <<EOF
object $commit
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000

Test release.
EOF
$ sed -n '1,8p' "$workdir/tag.txt"
object 6f...
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000

Test release.

Use the exact object ID from your own repository. type must describe that object's actual type. You can omit the message, but keeping a blank line and a short message makes the record easy to inspect. Do not add arbitrary headers after tagger: this installed command treats extra headers as an error by default.

4. Create the tag object safely

Redirect standard output to a new file first. A successful run prints the new tag object's ID and writes the object into the repository:

$ git -C "$workdir" mktag < "$workdir/tag.txt" > "$workdir/tag-id.txt"
$ tag_object=$(sed -n '1p' "$workdir/tag-id.txt")
$ printf '%s\n' "$tag_object"
a60453650015dec1370605d21a2d6f1bd46b2ca7

Your hash will differ. There is no success message apart from that identifier. The output file is new state, so do not point the redirection at a file you actually need unless overwriting it is deliberate. If the input fails validation, the command should fail before writing the tag object, but a shell redirection can still truncate its destination before Git even starts, so choose a disposable output path or check for an existing file first.

Checkpoint: Verify both the exit result and the stored object type:

$ test -n "$tag_object" && git -C "$workdir" cat-file -t "$tag_object"
tag
$ git -C "$workdir" cat-file -p "$tag_object"
object 6f...
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000

Test release.

cat-file -p checks the object contents, not merely that a command returned zero. Compare the headers with the source record if you are debugging a generated tag or a signing workflow.

5. Understand strict validation

Here is the surprising bit: mktag runs an fsck-style check before writing, and in this version its default is equivalent to strict mode. Messages that ordinary git fsck might only warn about get promoted to errors, so missing required metadata such as tagger can stop the write outright.

It also rejects extra object headers that git fsck would otherwise ignore. If a known, compatible repository deliberately contains such a header, you can relax only that check for one invocation:

$ git -C "$workdir" -c fsck.extraHeaderEntry=ignore mktag < "$workdir/tag.txt"
a60453650015dec1370605d21a2d6f1bd46b2ca7

Do not copy this configuration into a general Git configuration without understanding why the header exists: ignoring validation can let malformed or surprising objects into a repository. Likewise, --no-strict disables the equivalent of git fsck --strict, it does not make an invalid object reference valid. Prefer fixing the tag record and keeping the default checks.

6. Diagnose the common failures

A missing or misspelt target produces an object lookup error. Check the target without changing anything:

$ git -C "$workdir" cat-file -t "$commit"
commit

7. Decide whether you need a named ref

The object ID printed by mktag is not the same thing as a repository ref. Listing it with git cat-file proves the object exists, but git tag will not show release-1.0 until a ref actually points at it. For a normal annotated tag, create the ref only after checking the object:

$ git -C "$workdir" update-ref "refs/tags/release-1.0" "$tag_object"
$ git -C "$workdir" show-ref --tags
a60453650015dec1370605d21a2d6f1bd46b2ca7 refs/tags/release-1.0

This changes the repository's refs, so treat it as a deliberate state change. To undo this exact test ref, confirm the name first, then remove only that ref:

$ git -C "$workdir" update-ref -d refs/tags/release-1.0
$ git -C "$workdir" show-ref --verify --quiet refs/tags/release-1.0; printf 'status: %s\n' "$?"
status: 1

Deleting the ref makes the tag object unreachable from that name; it does not immediately erase the object itself. Do not use ref deletion as a substitute for checking which tag a production repository should actually retain.

Done means