5.4.4 Stamping Cache Impact
recommendedStamping does not make an entire build uncacheable. It adds workspace-status files as inputs to particular actions, and only those actions—and their downstream consumers—can lose reuse when the relevant status changes. The performance failure is therefore a blast-radius problem: frequently changing data placed in stable status repeatedly invalidates every stamped action that consumes it.
For rule-family integration patterns after the blast radius is understood, see H.11.2 Artifact Identity.
Trace the data before blaming the cache
--workspace_status_command=... runs a program at the beginning of each build.
The program prints key-value lines, which Bazel separates into two generated
files. User-defined keys beginning with STABLE_ go to
bazel-out/stable-status.txt. Other user-defined keys go to
bazel-out/volatile-status.txt.1
Those files do not affect every action automatically. A rule must support
stamping and arrange for one or both files to be consumed. Native and ecosystem
rules differ in their stamp attributes, defaults, and implementation details.
a custom rule can also declare ctx.info_file and ctx.version_file as action
inputs directly. Check the rule's documentation or inspect the action rather
than inferring behavior from the target's language.1,2
This gives a useful diagnostic chain:
workspace status key changes
↓
stable-status.txt changes
↓
an action that declares that file as an input is invalidated
↓
changed stamped output may invalidate downstream actions
The first two arrows do not imply the third for an unstamped target or for a rule that never consumes the status file. This is why “nothing caches” is a misleading diagnosis. The affected set can still be large—for example, if many release binaries embed the same changing identifier—but it is not universal.
At the action-cache boundary, treat stable-status.txt like an ordinary
declared input: its content can participate in the cache lookup for an action
that consumes it. Do not confuse that lookup with the ActionKey printed by
aquery. 5.2.3 bazel aquery — Action Graph explains why that field is not the complete
input-content or remote-cache key. This is the same affected-set reasoning used
for ordinary source edits in 5.4.3 Measuring Granularity Trade-offs: find the actions
that consume the changed input, then trace changed outputs into downstream
actions.
Stable means “invalidate when this changes”
The stable/volatile split is a rebuild policy, not a judgement about whether a value is important.
| Status class | User key spelling | Effect when only this status changes | Appropriate data |
|---|---|---|---|
| Stable | starts with STABLE_ | invalidates actions that depend on the stable file | release version or source revision whose change must produce a new artifact |
| Volatile | any other user key | Bazel updates the volatile file but deliberately does not invalidate dependent actions for that change alone | timestamp, CI attempt, or other observation that may change every invocation |
Bazel supplies several built-in BUILD_* keys, including volatile
BUILD_TIMESTAMP and FORMATTED_DATE. But BUILD_ is not the syntax that
makes a user key volatile: every user key without the STABLE_ prefix is put
in volatile status.1
The volatile exception has a consequence that is easy to miss. If only
volatile-status.txt changes, Bazel reuses the existing action result, so the
already-built artifact keeps the older embedded value. When another genuine
input changes and the action runs again, it sees the current volatile file.
Volatile status therefore means “use the latest value on the next necessary
execution,” not “regenerate the artifact on every build.”1
The stamping-status experiment makes that
policy observable rather than inferred. Its
workspace_status.sh
reads independently controllable stable and volatile values. The custom
versioned rule
explicitly consumes ctx.info_file and ctx.version_file. This narrowly scoped
experiment does not claim that --stamp automatically affects custom rules or
that every language rule consumes both files.
Run the deterministic
run_cache_experiment.sh
from the snippet root. It uses a fresh --output_base, writes a separate JSON
execution log for each invocation, and counts executions of the experiment's
StampedReport action:
bash tools/run_cache_experiment.sh
1 initial executions=1 output=release-a/attempt-1
2 volatile-only change executions=0 output=release-a/attempt-1
3 stable change executions=1 output=release-b/attempt-2
4 volatile-only change executions=0 output=release-b/attempt-2
5 ordinary input change executions=1 output=release-b/attempt-3
The measured blast radius is one action in this workspace. Builds 2 and 4 show reuse despite changed volatile status, including the intentionally stale value in the output. Build 3 proves that a stable change invalidates the consumer. Build 5 changes an ordinary declared input, executes the action, and only then captures the latest volatile value. In a real repository, repeat this method for each suspected consumer and its downstream actions. Do not extrapolate from one target to “the whole build.”
Decide: A release archive must contain the exact source revision, while its build timestamp is merely diagnostic. Which status class should each use?
Reveal
Use a stable key such as STABLE_GIT_COMMIT for the revision: changing commits
must invalidate the archive-producing action. Use a non-STABLE_ key for the
timestamp if a timestamp change alone must not rebuild it. If every release
must contain the time of that invocation, then the timestamp is a real
output-determining input and the resulting cache loss is intentional. Changing
its prefix cannot provide both a fresh timestamp and reuse of the old artifact.
Separate development and release configurations
The command-line --stamp and --nostamp flags work together with each rule's
stamp attribute. For rules whose attribute delegates to the command line
(commonly represented as stamp = -1), --stamp enables stamping and
--nostamp disables it. A rule can instead force stamping on or off, and Bazel
does not stamp binaries built in the exec configuration. Tests commonly default
to stamping off. These details are rule-specific, so --stamp is not a promise
that every target will consume status, and --nostamp cannot override a rule
that forces it on.1
A practical default is to keep ordinary development builds unstamped and enable both the status command and stamping in an explicit release configuration:
build:release --stamp
build:release --workspace_status_command=tools/workspace_status.sh
That avoids running repository-inspection commands on every development build and keeps volatile release metadata out of the development action graph. It also makes the cache trade-off visible at the call site:
bazel build //app:server
bazel build --config=release //app:server
Whether a Git commit belongs in stable status deserves a deliberate answer. For a release artifact that promises to identify its exact source, changing the commit should invalidate the stamped action. For a local artifact whose bytes need not carry provenance, disabling stamping is usually better than relabeling the commit as volatile and accepting stale embedded metadata. 3.2.3 Stamping & Build Metadata shows how to wire the status command and rule-facing stamping controls. The consultant's task here is to verify the invalidation boundary.
Diagnose repeated execution with evidence
When a stamped build executes more work than expected:
- Run the same target and configuration twice with
--explain=<file>and--verbose_explanations, then inspect the explanation for actions whose inputs changed. 2.4.4 Diagnosing Cache Misses develops that workflow.1 - Capture the status command's output twice. Look first for timestamps, random
IDs, dirty-tree markers, hostnames, or CI attempt numbers under
STABLE_. - Inspect the suspect action with
aqueryto see whether a workspace-status artifact is among its inputs. Remember thataquerydescribes the planned action. It does not prove a cache miss. - Check the rule's
stampcontract and the effective flags. Do not assume that astampattribute has identical values or defaults across rulesets. - Fix the policy, then repeat the same two-build experiment. Move data to volatile status only when stale-until-next-execution semantics are correct. otherwise keep it stable and narrow which actions consume it.
The last distinction matters most. A changing stable value is not inherently a bug. It is costly only when its rebuild semantics are unnecessary or its consumer set is wider than the artifact contract requires.
For a larger invocation, the execution logs establish which actions actually ran. A trace profile or 5.4.5 Bazel Invocation Analyzer (BIA) can then help quantify where those misses land in elapsed time. Keep causality in that order: first prove invalidation and its affected set, then assess its timing impact.
Stable status invalidates only actions that consume it, not the entire build.
Put a value under STABLE_ when changing it must produce a new stamped artifact.
put frequently changing observational data in a non-STABLE_ key only when it
may remain stale until some other input causes execution.
Treat --stamp behavior as rule- and configuration-dependent. Diagnose the
actual consumer action and its effective flags, and prefer a separate stamped
release configuration over paying the provenance cost in every developer build.
Check your understanding · 4 questions
1.Match each workspace-status key to its rebuild policy:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.For a build where one stable status value changes, classify each claim:
Choose True or False for each sentence
3.A release artifact must identify its exact commit, but local development artifacts need no embedded provenance. Which policy best preserves correctness and reuse?
Select one answer
4.Which steps provide evidence for diagnosing repeated execution in a stamped build?
Select all that apply
Footnotes
-
Commands and Options — Bazel 9 workspace-status partitioning, stable and volatile invalidation contracts, and the interaction between
--stampand rule attributes ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
ctx —
ctx.info_fileandctx.version_fileexpose stable and volatile workspace-status files to Starlark rule implementations ↩