5.1.2 First Investigation: Two Builds That Look Identical

Two developers report the same command, bazel build //app:bundle, but only one invocation reuses the expected work. We will investigate this as a staged case. Each stage reveals a small piece of evidence; before continuing, we will decide which hypotheses it weakens and which probe is justified next.

The cards below are a teaching case, not captured terminal output. Values such as R17, OB-A, and RC-A are case identifiers, not strings Bazel prints.

Stage 0: the report

Invocation A

reported command: bazel build //app:bundle
reported result: expected work was reused

Invocation B

reported command: bazel build //app:bundle
reported result: app actions executed again

Apply 5.1.1 Evidence-Led Investigation Contract before naming a cause:

For //app:bundle, two reported invocations had different reuse outcomes. Their equivalence has not yet been established.

The visible command and outcome are observations. “Remote cache failure,” “Bazel regression,” and “non-hermetic rule” are interpretations. We keep four competing hypotheses:

  • H-source: the revision or dirty working tree differs;
  • H-config: rc files or explicit options produce different effective configurations;
  • H-local-state: the server process or the on-disk output-base state differs;
  • H-shared-cache: disk/remote cache endpoint, policy, or history differs.

Stage 1: establish source and tool identity

The developers attach records made with these ordinary commands:

git rev-parse HEAD
git status --short
bazel --version

We store the returned values in the incident system rather than inventing terminal output here:

FieldInvocation AInvocation B
revisionR17R17
git status --shortemptyempty
Bazel versionV8V8
host / target platform recordP-linux / P-linuxP-linux / P-linux

This evidence weakens H-source for the surfaces inspected. It does not prove that every real input is equal: an undeclared host tool or environment value could still differ.1 But changing the revision is no longer the most discriminating next probe.

The best next comparison is effective configuration. It is read-only with respect to the retained-state hypotheses and tests a common reason that matching command tails are misleading.

Stage 2: reveal the invocation Bazel received

Each developer repeats the original request with rc announcements enabled:

bazel build --announce_rc //app:bundle

We preserve the announcements and reduce them to this case record:

FieldInvocation AInvocation B
workspace rcRC-workspaceRC-workspace
additional rcnoneRC-user
named confignonenone
compilation mode contributed by rcfastbuildopt
other recorded build optionsmatchedmatched

Bazel reads defaults from layered rc files, and later contributions can change the effective invocation even when the visible command matches.2 We have now found a concrete prediction for H-config: if compilation mode is the relevant difference, the reuse outcome should follow that option under an otherwise matched invocation.

We have not proved H-config yet. The original invocations may also have different server, output-base, or shared-cache histories. Changing the option on those two original machines would leave those alternatives entangled.

Stage 3: separate the three state surfaces

Before the controlled rerun, the developers record local and configured cache provenance:

bazel info output_base
bazel info server_pid

The incident cards use aliases for the actual returned paths and process IDs:

SurfaceInvocation AInvocation BMeaning
server process / in-memory analysis stateSERVER-A, prior relevant invocationSERVER-B, no recorded prior invocationDifferent process histories
output base / on-disk action cache and outputsOB-A, populatedOB-B, sparseDifferent workspace-local histories
configured disk cachedisableddisabledMatched policy
configured remote cacheCACHE-C, read-onlyCACHE-C, read-onlyMatched endpoint and policy

These are three different surfaces. The server holds in-memory package, dependency-graph, and analysis state across commands; its identity is selected through the output base.3 The output base also contains on-disk local state, including the action cache and outputs, which does not disappear merely because the server process stops. An explicitly configured disk or remote cache is a third store with its own endpoint and read/write policy.4

Therefore the original A/B pair cannot test H-config cleanly. It differs in the server process and output-base-local history, even though its remote-cache policy matches. A generic “make B fresh” experiment would be worse: replacing the server and output base together would change two evidence surfaces.

Stage 4: choose one controlled probe

We use one controlled environment and keep these fields fixed:

revision=R17; dirty=false; Bazel=V8; platform=P-linux
server=SERVER-C; output_base=OB-C
disk_cache=disabled; remote_cache=CACHE-C/read-only
target=//app:bundle

First we establish the fastbuild baseline in that environment. Then, without changing the server process, output base, cache endpoint, cache policy, source, platform, or target, we vary only the compilation mode contributed by the rc layer. The completed probe card is:

Controlled runOnly varied fieldObserved reuse outcome
C1compilation_mode=fastbuildexpected reuse
C2compilation_mode=optaffected app actions execute
C3compilation_mode=fastbuildexpected reuse returns

This is an A/B/A probe. C3 matters: it checks that the outcome follows the selected mode rather than a one-way change in server, output-base, or external cache history. The contents of OB-C naturally evolve as each run completes; the reversal does not pretend to freeze them. It tests whether the baseline result remains recoverable after the opt run instead of confusing monotonic cache population with the option change. Compilation-affecting options are part of the action identity; changing such an option can correctly require different work rather than indicating a broken cache.5

H-config is supported for this case. The probe weakens a server-replacement or cache-policy change as the explanation of the controlled difference because those named surfaces remained fixed across C1–C3. It does not prove that local cache contents had no interaction with the sequence, nor that H-local-state was impossible in the original A/B pair; its state histories were not matched.

Stage 5: write the bounded conclusion

Observed:
  The original A and B invocations reported different reuse for //app:bundle.

Provenance limit:
  Their source, Bazel version, and platform matched, but their server processes
  and output-base-local histories did not.

Discriminating evidence:
  Rc announcements exposed fastbuild versus opt. In a controlled A/B/A probe,
  only compilation_mode changed; server process, output base, disk/remote cache
  policy, source, platform, and target remained fixed. The reuse outcome followed
  the mode and returned with the baseline.

Bounded diagnosis:
  For R17, V8, P-linux, //app:bundle, and the C1–C3 environment, the unexpected
  comparison was explained by different effective compilation modes, not by a
  failure of the fixed remote-cache policy. The original pair alone cannot
  establish whether its differing local state also affected either result.

Next action:
  Decide whether RC-user should override the workspace mode. After changing the
  policy, repeat the matched comparison rather than clearing retained state.

We reached a useful answer without query, profiles, or execution logs. Those tools become appropriate when the provenance matches and the remaining question is about graph shape, time distribution, or individual action reuse. The first investigation skill is deciding whether two invocations deserve that deeper comparison.

key takeaway

Matching command strings are only the start of an A/B investigation. Record source, Bazel, rc, and platform identity; distinguish the server's in-memory state from output-base-local action state and from disk/remote cache policy; then vary one surface while naming everything held fixed. Here the staged evidence rejected a source explanation, exposed an rc difference, and used an A/B/A probe to earn a diagnosis bounded to one target and environment.

Check your understanding · 3 questions

1.Two developers report the same visible bazel build command but different reuse outcomes. What is the strongest first conclusion?

Select one answer

2.Which differences can make two matching visible command strings insufficient evidence of equivalent builds?

Select all that apply

3.A provenance comparison finds both a different Bazel version and a different rc option. What is the best next probe?

Select one answer

0 of 3 answered

Footnotes

  1. Hermeticity — source identity, isolation, and hidden host inputs that can make apparently equivalent builds diverge

  2. Write bazelrc configuration files — rc-file locations, imports, precedence, command scopes, and named configurations

  3. Client/server implementation — output-base server identity and in-memory metadata retained across invocations

  4. Remote Caching — disk/remote cache stores and explicit read/write behavior, distinct from output-base-local reuse

  5. Command-Line Reference — compilation mode semantics and output-affecting option behavior