5.7.2 Execution Log Analysis
An execution log is the evidence to collect when “Bazel rebuilt it” is only the symptom. It records the spawns that participated in an invocation, including their arguments, environment, inputs, outputs, execution metadata, and cache information. Comparing two logs lets you move from a rebuild cascade to the first action whose recorded state actually changed.1
This is different from 5.2.3 bazel aquery — Action Graph: aquery describes actions created by
analysis, while an execution log describes spawn-level execution evidence from
one invocation. It is also different from the experimental execution graph log
in 5.4.6 Execution Log Graph.
The shared-policy controls in 3.2.6 Hermeticity Settings reduce common host leaks. Paired logs are the next step when a divergence remains after those controls are in place.
Capture a comparable pair
Use the compact format for new investigations:
bazel build //app:release \
--execution_log_compact_file=/tmp/exec-before.log
bazel build //app:release \
--execution_log_compact_file=/tmp/exec-after.log
The stable flag is --execution_log_compact_file. Bazel 9 documents the file as
a Zstandard-compressed stream of length-delimited ExecLogEntry protobuf
messages. The older binary and newline-delimited JSON formats remain available
through --execution_log_binary_file and --execution_log_json_file, but Bazel
recommends compact because it is significantly smaller and cheaper to produce.
The three output flags are mutually exclusive.2
Do not attach a universal size ratio to that recommendation. Log size depends on the action graph and its input structure. Compact encoding retains structural back-references instead of eagerly repeating large input sets, so the saving can be dramatic, but a measurement from the Bazel repository is an example rather than a capacity-planning constant.3
The compact format appeared experimentally during the Bazel 7 line and is
documented as stable in Bazel 8 and later. Older 7.x material may therefore show
--experimental_execution_log_compact_file. Check bazel help build for the
binary that will produce the log instead of copying that spelling into a shared
.bazelrc.3 Bazel 7.2 added automatic upload of an enabled compact log to a
configured Build Event Service. Collection is still not enabled by default, and
retrieval or analysis depends on what the BES backend exposes.4
A useful pair changes only the factor under investigation. Keep the target, revision, Bazel version, configuration, platform, toolchain, and relevant host environment recorded. First compare repeated builds on one machine. Compare CI with a developer machine only after that baseline behaves as expected. Otherwise the diff mixes the suspected cause with unrelated configuration drift.1
Read the first divergence, not the largest cascade
The most visible difference is often downstream damage. A changed generated header can invalidate hundreds of consumers, but those consumers are not hundreds of independent root causes. Start at the earliest meaningful difference and classify it:
| Difference in the producing spawn | What it means | Next question |
|---|---|---|
| Input path or digest | The declared input state changed | Was the edit expected, or did an unstable generated file enter the graph? |
| Argument or environment value | The action identity changed | Which rc setting, rule attribute, toolchain, or host value supplied it? |
| Platform or execution property | The execution configuration changed | Are the two invocations actually in the same comparison cohort? |
| Output digest, with no recorded input difference | Evidence of possible non-hermeticity | Can two independent cache-bypassing runs reproduce the difference? |
The last row is a lead, not an automatic verdict. The log can show that recorded inputs agree while output bytes differ. It cannot prove that every real input was declared. That mismatch is exactly what makes an action non-hermetic. Trace the first changed output to its producer, then inspect undeclared clocks, randomness, filesystem state, network access, locale, and unstable ordering as candidates. 2.3.3 Non-Determinism Sources explains how those sources propagate through the build.
Likewise, “the action ran again” does not by itself prove cache poisoning. A changed command, environment, input, platform, or cache-read policy explains an ordinary miss. 5.5.3 Remote Cache Diagnostics provides the stronger controls needed to prove that the same action identity reused a wrong cached result.
Choose a comparison tool
Bazel's source tree contains //src/tools/execlog:parser, which accepts a pair
of logs, converts them to text, and aligns actions for an ordinary text diff.
It also contains //src/tools/execlog:converter for converting among supported
formats. These are source-tree targets, not commands installed beside every
Bazel binary, so build them from a matching Bazel checkout.1
For compact logs, bb explain performs a structural comparison directly. It can
surface changed inputs, arguments, environment values, and outputs, then group
their transitive effects so the initial cause is not buried under downstream
re-execution. It can compare local log files without a BuildBuddy account.
BuildBuddy-backed workflows can also fetch logs by invocation identity. Treat
this as an external BuildBuddy CLI capability, not as a Bazel subcommand.3
bb explain /tmp/exec-before.log /tmp/exec-after.log
When the comparison isolates two different output artifacts, use a byte- or format-aware tool for the second question: how do the artifacts differ? Diffoscope is useful for archives and binaries because it can expose embedded timestamps, paths, ordering, or metadata. It does not explain why Bazel chose to execute an action, and it does not read execution logs. Use it after the log comparison has identified the relevant producer and outputs.3
Finally, do not expect --execution_log_sort to normalize compact logs. That
flag sorts only binary and JSON logs. Compact logs are never sorted. Use a
structure-aware comparator rather than a raw byte diff of two compact files.2
Capture two controlled invocations with --execution_log_compact_file, keeping
the workload and execution context matched. Compare spawn structure to locate
the first changed input, argument, environment value, platform property, or
output instead of treating every downstream rerun as a root cause.
Use Bazel's source-tree parser for aligned textual comparison or bb explain
for a structural compact-log diff. An output change without a recorded input
change is evidence to investigate non-hermeticity, not proof by itself. Confirm
it with independent runs and inspect the isolated artifacts with a tool such as
Diffoscope.
Check your understanding · 3 questions
1.A small source edit re-executed hundreds of actions. What should you inspect first in paired execution logs?
Select one answer
2.Match each tool or flag to its role:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
3.Decide which conclusions are supported by execution-log evidence.
Choose True or False for each sentence
Footnotes
-
Debugging Remote Cache Hits for Remote Execution — controlled same-machine and cross-machine comparisons, execution-log contents, and Bazel's parser and converter workflow ↩1 ↩2 ↩3
-
Bazel Command-Line Reference — current execution-log flags, protobuf encodings, Zstandard compression, mutual exclusion, and sorting scope (verified with Bazel 9.0.0) ↩1 ↩2
-
bb explain: Understanding Why Bazel Rebuilds — compact-log structure, local-file and hosted comparison workflows, transitive root-cause analysis, non-hermetic output detection, and Diffoscope handoff ↩1 ↩2 ↩3 ↩4
-
What's New in Bazel 7.2 — compact-log overhead, opt-in collection, and Bazel 7.2 upload to a configured Build Event Service ↩