2.4.4 Diagnosing Cache Misses

Unexpected rebuilds are a different debugging problem from failed builds. When bazel build succeeds but reruns work you expected to be up to date, start with --explain=/tmp/explain.log: Bazel writes, for each build step, why it ran or why it considered the step up to date. --verbose_explanations only works alongside --explain, but it becomes much more useful when the cause is "the command changed" because Bazel includes the newer command details in the log.1 This is the first-response tool for cache misses at this level. For failing actions and sandbox problems, use 2.3.4 First-Response Debugging Flags instead. Read the explain log as a human-readable view of 2.4.3 What Makes a Cache Hit.

Find why work reran: start with the explain log, then check the cause
The log describes the reuse decision. Add more detail only when the reason is unclear.
SYMPTOM
The build succeeds but steps rerun when you expected cache reuse
Success path, surprising invalidation
EXPLAIN LOG
Record why each step ran or stayed up to date
First-response view of action reuse
--explain=/tmp/explain.log
INPUTS
Declared inputs or graph edges changed
Same story as action keys
COMMAND
The recorded command or tool metadata changed
Verbose explanations in the log
CONFIG
Effective flags changed between invocations
announce_rc shows which rc file set each flag
STAMP / ENV
Build metadata (stamping) or environment fields changed
Check workspace-status inputs and stamped actions
STILL UNCLEAR
Compare execution logs when the reason stays ambiguous
Deep diff of action definitions
--execution_log_json_file=/tmp/execution.json
Read the log as plain-language reuse output. Add --verbose_explanations and --announce_rc only when the line you care about needs more detail.

Start with an explain log

bazel build //app:server --explain=/tmp/explain.log

# Add these when the first log points at command or config drift
bazel build //app:server \
  --explain=/tmp/explain.log \
  --verbose_explanations \
  --announce_rc

--announce_rc makes Bazel print which rc files supplied options, which is often the missing clue when local and CI do not build with the same effective flags.1 For a short debugging session, keeping --explain=... in .bazelrc can also be useful, but remove it again when you are done because the logging has a performance cost.1

What the log is actually telling you

The explain file does not invent a new cache model. It surfaces Bazel's existing reuse decision in plain language. If an action reran because one of its inputs changed, that is the expected consequence of the cache-key rules from 2.4.3 What Makes a Cache Hit and of Bazel's general incremental-build guarantee: if the inputs or the build command change, Bazel reruns the affected step.2

When the interesting line is "the command changed", switch on --verbose_explanations. Common reasons are different .bazelrc layers, an unexpected --config=..., or build flags drifting between invocations.1,3 Toolchain or compiler changes often show up in the same bucket: Bazel's action identity can depend on command details such as compiler flags, library locations, and other tool-related metadata, not just source files.3 The same flag churn also tends to discard the in-memory analysis cache, which is why iterative workflows get slower even before execution starts.4 This is where 3.2 Project Configuration becomes the right follow-up chapter: the cache miss is often real, but the hidden cause is configuration hygiene rather than a source edit.

Stamping is another common source of "why did this rerun?" confusion. --workspace_status_command writes stable and volatile status files before the build. Stable keys invalidate stamped actions when they change. Volatile keys are treated as expected churn and do not invalidate stamped actions by themselves.1 Specifying --stamp does not automatically force a rebuild when nothing else changed, but it does make those workspace-status files relevant to the action definition.1 If explain output keeps pointing at workspace-status-related actions or stamped binaries, the question is usually not "is caching broken?" but "should this build metadata be stamped in everyday development at all?" That connection is covered in 3.2.3 Stamping & Build Metadata.

Machine-specific misses usually point back to the same leak paths from 2.4.3 What Makes a Cache Hit and 2.3.1 Hermeticity: environment differences such as $PATH change action definitions across machines, while host tools outside the workspace are a warning sign that the environment is not being modeled cleanly.5 The --incompatible_strict_action_env setting exists precisely to shrink this class of problems.6

If the first --explain log is too vague, keep the same target and add one clue at a time instead of changing everything at once:

  • --verbose_explanations when Bazel says the command changed
  • --announce_rc when you suspect .bazelrc or --config=... drift
  • stamping checks when workspace-status-related actions are the noisy input

This keeps the debug session close to the rebuild you are trying to explain instead of jumping immediately to heavier tools.

When explain is no longer enough

--explain is intentionally a first-response tool. It is great at answering "why did Bazel rerun this build step?" but not at exhaustively diffing two invocations. If the log shows that something changed but you still cannot tell which input, environment field, or action detail diverged, the next tool is execution-log comparison in 5.7 Cache & Execution.7 That is the deeper workflow for non-hermetic actions, cross-machine drift, and cache misses that survive the obvious checks.

key takeaway

Use --explain when the build succeeds but rebuilds unexpectedly. Add --verbose_explanations for command drift, --announce_rc for rc-file drift, and treat the resulting log as a readable explanation of 2.4.3 What Makes a Cache Hit. Most "mysterious" misses turn out to be configuration, stamping, or environment differences, and the ones that do not are the handoff point to 5.7 Cache & Execution.1,4,5,7

Check your understanding · 3 questions

1.What problem does --explain address, and how does it differ from --verbose_failures?

Select one answer

2.Which flags complement --explain when diagnosing why a build step was rebuilt?

Select all that apply

3.True or false: choosing first-response cache-miss diagnostics.

Choose True or False for each sentence

--explain=/path/to/log is the starting point when a build succeeds but reruns work you expected to be cached.
--verbose_explanations is useful by itself even when --explain is not enabled.
--announce_rc helps when local and CI builds may be using different rc-file or --config settings.
Execution-log comparison in Level 5 is the next step when explain logs do not identify the changed input or action detail.
0 of 3 answered

Footnotes

  1. Commands and Options--explain, --verbose_explanations, .bazelrc, --announce_rc, --config, workspace status, and --stamp 1 2 3 4 5 6 7

  2. Build programs with Bazel — correct incremental rebuilds rerun steps when inputs or the build command change

  3. Bazel Glossary — definitions of action key and configuration, including build flags and tool-related metadata that affect action identity 1 2

  4. Optimize Iteration Speed — flag changes and startup-option changes discard analysis cache and slow iterative workflows 1 2

  5. Remote Caching — action definitions include environment variables. Differing $PATH and untracked host tools break predictable cache sharing 1 2

  6. .bazelrc flags you should enable--incompatible_strict_action_env as a practical guard against environment leakage and large cache misses

  7. Bazel and action (non-) determinism--explain example for unexpected rebuilds and execution-log comparison for deeper non-determinism diagnosis 1 2