3.2.3 Stamping & Build Metadata

recommended

Stamping is Bazel's way to carry traceability data into built artifacts without checking generated version files into the repo. For a maintainer, the important question is not just "how do I print a git SHA?" but which metadata should change the artifact's cache key, which metadata should stay observational, and where stamping belongs in the project's default configuration.1,2

From workspace status script to stamped artifacts
Stable and volatile keys split early. Each rule's stamp setting decides whether metadata reaches its outputs.
STATUS SCRIPT
workspace_status_command prints one KEY value pair per line
Executable must exit zero or the build fails
echo "STABLE_GIT_COMMIT $(git rev-parse HEAD)"
Bazel splits script output by key prefix into two status files
STABLE STATUS
Keys starting with STABLE_ map to stable-status.txt
Treat as identity-bearing metadata for release builds
bazel-out/stable-status.txt
Remote / action cache
When this file changes, dependent stamped actions rerun and cache keys move.
VOLATILE STATUS
Every other key maps to volatile-status.txt
Timestamps, CI ids, and other always-moving observability
bazel-out/volatile-status.txt
Remote / action cache
If only this file changes, Bazel keeps stamped actions cached as unchanged.
STAMP GATE
Per-rule stamp can force on, force off, or defer to --stamp
There is no global substitution. Binaries and tests have different defaults.
stamp = 0 / 1 / -1 with --stamp when deferred
ARTIFACT
Stamped outputs carry traceability metadata into binaries, images, and packages
Release path wires provenance without checked-in generated version files
image labels · jar manifests · embedded build info
Reserve STABLE_ for values that should change artifact identity. Keep frequently changing values in volatile keys so observability does not defeat the cache.

Three pieces have to line up

First, --workspace_status_command points to an executable Bazel runs before the build. It may print zero or more KEY value pairs, one per line, and it must exit successfully or the build fails.1 Bazel then splits that output into bazel-out/stable-status.txt for keys whose names start with STABLE_ and bazel-out/volatile-status.txt for everything else.1

#!/usr/bin/env bash
echo "STABLE_GIT_COMMIT $(git rev-parse HEAD)"
echo "CURRENT_TIME $(date +%s)"

Second, the consuming rule has to support stamping. Stamping is not a global text substitution pass. Bazel only embeds workspace status through rules that opt into the stamp attribute.1,2 Third, when a rule defers to the command line, the build needs stamping enabled, typically with --stamp on the release path rather than on every local build.1,3

The stamping-status snippet exposes the status-file side of that plumbing in a minimal workspace: workspace_status.sh emits one stable and one volatile key, and its custom versioned rule explicitly consumes both generated status files. That experiment intentionally does not model a language rule's stamp attribute. Production rule support and the effective --stamp setting remain the other pieces of the chain.

Stable and volatile keys mean different promises

Stable keys participate in invalidation. If stable-status.txt changes, stamped actions that depend on it rerun.1 That makes STABLE_GIT_COMMIT or a release version a good fit, because a new source revision should usually produce a distinct release artifact.1,4

think

Trace: Your release target is stamped. The status script prints the same STABLE_GIT_COMMIT as the previous build, but a fresh CURRENT_TIME every time. The artifact still rebuilds on every CI run. Can the volatile timestamp alone explain that?

Reveal

No. Volatile-only churn is designed to be observational metadata: Bazel updates volatile-status.txt, but if that is the only changed status file, stamped actions should remain cached.

That makes the timestamp a decoy during debugging. If the artifact still rebuilds, look for a changing STABLE_ key, command or configuration drift, a rule that depends too broadly on status files, or another real input change. Start with --explain so the next theory is tied to an invalidation reason.

Volatile keys are deliberately different. Bazel still updates volatile-status.txt, but if that file is the only thing that changed, Bazel pretends it did not and keeps stamped actions cached.1 That is why timestamps, CI build numbers, and similar always-changing values belong in non-STABLE_ keys.1 When a stamped target keeps rebuilding and you need evidence rather than theory, start with --explain from 2.4.4 Diagnosing Cache Misses. The broader workflow continues in 5.4 Performance, and the specific failure mode from bad key choice is isolated in 5.4.4 Stamping Cache Impact.

That split is easy to misuse. Workspace status is a hermeticity gray area: if an action depends too broadly on stable-status.txt, it rebuilds more often than intended and loses remote-cache value. Keep the stable data small, and only thread it into artifacts that really need it.5

Keep stamping on the release path

Most projects should not run git or version-discovery logic on every build. The recommended pattern is to keep --workspace_status_command behind a named config such as build:release, then activate it together with --stamp only when you are intentionally producing a traceable artifact.3 That fits the rc-layering model from 3.2.1 .bazelrc Hierarchy: shared policy lives in the workspace rc, but expensive or release-only behavior stays opt-in.

build:release --workspace_status_command=./tools/workspace_status.sh
build:release --stamp

This is also the bridge to versioning. A workspace_status_command can compute a git-derived version string and make it available to stamped binaries, packages, or images, which is exactly how the release patterns in H.11.3 Release Identity connect source history to shipped artifacts.4

Language rules decide how that metadata reaches their outputs. In rules_go, x_defs maps Go variables to link-time strings and can substitute workspace-status keys such as {STABLE_GIT_COMMIT}. Changing a stamped value relinks the binary rather than recompiling its packages.6 That is a concrete rule-specific implementation of the general chain above, not a promise that every language ruleset uses x_defs.

key takeaway

Treat stamping as release-oriented metadata plumbing, not as a default state of the repo. Emit a small set of intentional keys, reserve STABLE_ for values that should genuinely change artifact identity, keep timestamps and CI noise volatile, and enable --stamp only where the traceability benefit is worth the cache cost. Once you want those stamped values to become a versioning scheme instead of raw metadata, the next step is H.11.3 Release Identity.

extra

Why --stamp sometimes seems to do nothing

--stamp is only one switch in the chain. Rules may force stamping off, force it on, or defer to the command line. The common *_binary default is stamp = -1, where --stamp decides, while *_test rules default to stamp = 0, which keeps them unstamped regardless of --stamp.1 So if a target does not show the metadata you expected, the missing piece is often rule support or rule defaults, not the status script itself.1,2

Check your understanding · 3 questions

1.A STABLE_GIT_COMMIT key changes between builds. What happens to stamped actions that depend on stable-status.txt?

Select one answer

2.True or false about stamping behavior:

Choose True or False for each sentence

Test rules (e.g., *_test) default to stamp = 0, meaning --stamp does not enable stamping for them.
Setting --stamp alone is sufficient to embed workspace status in any rule that supports the stamp attribute.
Timestamps and CI build numbers should go into STABLE_-prefixed keys to ensure artifact traceability.

3.Why should --workspace_status_command be placed behind a named config like build:release rather than in the default build section?

Select one answer

0 of 3 answered

Footnotes

  1. Commands and Options--workspace_status_command, stable vs volatile status files, example output keys, and --stamp semantics 1 2 3 4 5 6 7 8 9 10 11

  2. Bazel Glossary — definition of stamping and the requirement that the consuming rule supports stamp 1 2 3

  3. .bazelrc flags you should enable — keep --workspace_status_command behind a release config instead of enabling it for every build 1 2

  4. Versioning releases from a monorepo — using workspace_status_command and stamping to carry git-derived versions into release artifacts 1 2

  5. How to keep a Bazel project hermetic? — why depending too broadly on workspace-status data increases rebuilds and hurts remote-cache effectiveness

  6. rules_go repository mapdefines_and_stamping.md documents x_defs, workspace-status substitution, and the link-only invalidation boundary for Go binaries.