Because the length of an abbreviated git hash depends on how many objects the repository holds. The same commit can print seven characters in one clone and ten in another, so a version string built from it differs. Fix the length, for example to twelve characters, or use the full hash.
What goes wrong
Many projects stamp a version like 2.4.1-3-gabc1234 into the binary or a generated file, using git describe or git rev-parse with a short option. It feels deterministic, since the commit is fixed.
The Reproducible Builds documentation explains the catch. The number of hexadecimal characters in an abbreviated hash depends on the number of objects in the repository. Git quotes the setting in its manual: if the abbreviation length is unspecified or set to auto, a value is computed from the approximate number of packed objects in the repository, chosen so abbreviated names stay unique for some time.
Two things change that number:
- the repository grows, even from commits on branches you do not release from,
- a shallow clone has fewer objects by design.
So a CI job that clones with limited depth can produce a different string from a full clone of the same commit. Nothing in your source changed, but the output did.
Fixes
The docs recommend a fixed or no truncation. Any of these works:
git describe --abbrev=12
git rev-parse --short=12 HEAD
git config core.abbrev 12
Setting the abbreviation to no gives the full length names. Pick one length, put it in one place, and use it everywhere your build asks git for an identifier. The minimum length git allows is 4, but pick something comfortably longer than your repository is ever likely to need.
If you use a shallow clone for speed, note that git describe also needs tags and history to compute its distance and tag name. A shallow clone can change the tag based part of the string. If you rely on describe output, make sure the build job fetches enough history, or derive the version from a source file.
Other version string habits to avoid
The same docs page names two counter examples: using the current date, and using an incremental build counter. Neither says anything about the source, and both change on every build. The date of the build is hardly useful, since old code can be compiled long after release.
Better sources for version information, per the docs:
| Source | Why it works |
|---|---|
| A dedicated version file | Changes only when a person changes it |
| The changelog | Reviewed with the release |
| Version control | Identifies source content; use a fixed hash length |
If you need a date, take it from the changelog or the version control system, not the clock. The SOURCE_DATE_EPOCH convention exists for this purpose. See which value to use.
Test it
- Clone the repository fully and build.
- Clone it shallowly, at the same commit, and build.
- Compare checksums.
- If they differ, search the diffoscope output for the version string.
This is a five minute test that catches a surprising number of mismatches. Pair it with a build in a second directory and a second timezone.
Quick check
Is the full hash always safer?
For reproducibility, yes, since its length never changes. Short hashes are friendlier to read, so many teams fix a length instead.
Does a fixed length remove the shallow clone problem?
For the hash, yes. Tag based names from git describe can still depend on available history.
Safeguard's SBOM and dependency scanning, generally available, records component versions from your repository. The free scan at gold.safeguard.sh/scan is an easy way to see them.
Sources
Last reviewed: 19 October 2026.