6.2.1 How Remote Cache Keys Work
A shared remote cache turns an action identity into a promise between producers and consumers: if two clients ask for the same key, either may receive the same result. That promise is safe only when the identity represents every declared choice that can affect the output and the action has no output-relevant hidden input. A matching target label—or even a matching digest by itself—is not that proof.
6.1.4 Action and Result Lifecycle placed the cache lookup on one branch of an action's lifecycle. Here we examine the identity used at that branch. The introductory model in 2.4.3 What Makes a Cache Hit still applies: think in actions, not targets, and start with inputs, instructions, and execution context. The Level 6 question is whether two different clients are justified in treating those things as equivalent.
Keep two earlier mechanisms distinct while answering it. 5.7.1 Cache Internals Reference separates the local output-base action cache from remote AC/CAS layers, while 5.9.4 Incrementality Mechanism explains why Skyframe dirty checking and change pruning may avoid or reach an action-cache lookup. Neither local mechanism is evidence of cross-client reuse.
Configuration influences identity through the action it creates
Suppose Linux CI and a developer laptop both request //app:compile. The label
identifies the target; the label together with a build configuration identifies
a configured target. Changing a configuration flag does not change the label,
but a relevant configuration change may produce a different configured target.
Analysis may then select different source files, command-line arguments, action
environments, tools, execution platforms, or declared outputs. Remote reuse is
decided for each resulting action from its action identity and cache key, not
once for the configured target as a whole.
A configured target may create zero, one, or many actions. For each action,
Bazel incorporates its declared inputs, command, and execution context into the
action identity. In REAPI, the lookup key is the digest of the canonical binary
encoding of the complete Action message. That message refers to a Command
and an input root, but it also has key-bearing fields of its own. Neither the
action identity nor its cache key is the configured-target identifier.1
For a remote action, use this more precise checklist:
Action.input_root_digestaddresses the rootDirectoryof the declared input tree. Its directory nodes contribute paths and file-content digests. Declared tools are inputs too, so a different compiler binary normally changes the identity even if both tools have the same friendly name.Action.command_digestaddresses aCommandwhose fields include arguments, environment variables, declared output paths, working directory, requested output metadata, and output-directory format.- In the protocol vendored by Bazel 9.1.0,
Actionalso directly containstimeout,do_not_cache,salt, andplatform. Their serialized values are part of the action digest. A truedo_not_cacheprevents caching in addition to participating in the message identity. DirectAction.platformwas added in REAPI v2.2; the olderCommand.platformfield remains deprecated for compatibility, and the protocol tells clients to set both while servers prefer the directActionfield.1 - Build configuration, target platform, execution platform, and toolchain
selection matter when they change the referenced input root or
Command, or any directActionfield. Their labels need not be literal fields in the cache key; their selected files, arguments, environment, paths, properties, and other encoded action values are what make the concrete action different. - The chosen digest function is part of the client/service contract. A digest identifies bytes under that function; it does not say that two differently declared computations are semantically interchangeable.
This distinction prevents an easy but costly mistake: adding a platform name or toolchain label to an operator-created namespace cannot repair an action that failed to declare the actual tool or system library. Conversely, two platform labels do not require different results when analysis produces an equivalent, correctly modeled action. Compare the concrete action dimensions, not the label spelling.2,3
“Action cache” names three different surfaces
The abbreviation AC is overloaded in Bazel discussions. Qualify it until the context is unmistakable.
| Surface | What the key finds | What it does not establish |
|---|---|---|
| Bazel's local action-cache state | Metadata used to decide whether outputs already in this output tree remain valid | A shareable remote protocol object or proof that another client produced the result |
| HTTP remote cache | An HTTP cache entry under Bazel's action-cache namespace, plus separately addressed content blobs | That REAPI protobuf services or remote execution participated |
REAPI ActionCache | An ActionResult addressed by the digest of an Action; that Action refers to a Command and an input root | The output file bytes themselves |
The first surface is Bazel-local persistent state under the output_base. It
indexes outputs in the local output tree and can be inspected with
bazel dump --action_cache. Its internal actionKey is related to, but not the
same stored key as, the remote action digest.4
The two remote presentations share the AC/CAS idea but not the same wire
contract. Bazel's HTTP cache protocol exposes action-result metadata and content
blobs through HTTP endpoints. A REAPI cache instead exposes protobuf/gRPC
services. Its ActionCache serves ActionResult metadata, while its
content-addressable storage (CAS) stores blobs under content digests.
ByteStream is a way to transfer blob bytes; it is not a third kind of cached
result.4,5
On a REAPI hit, the ActionResult can name output files and their digests. The
client uses those digests to obtain required bytes from CAS, possibly through
ByteStream. Finding the metadata therefore does not prove that every
referenced blob is still reachable. 6.2.2 Setting Up a Shared Remote Cache follows the
actual exchange, and 6.2.4 Wrong Cache Result Diagnostics handles incomplete or
wrong shared state.
Equal declared identity is necessary, not sufficient
Imagine a code generator that declares its template, executable, arguments,
and output but also reads /etc/company-region. Two clients can construct the
same declared action identity while observing different region files. If the
first client publishes its result, the second can receive a genuine cache hit
for the wrong region.
No digest collision is required. The declared inputs really are equal, the
ActionResult really points to the bytes the producer uploaded, and the CAS
digest really identifies those bytes. The failure is earlier: the declared
model omitted an ambient input. Hermeticity must make the recorded world match
the real one, and determinism must make equivalent executions produce stable
bytes.2,6 2.3.1 Hermeticity develops that correctness
contract.
Before allowing two producer cohorts to share cache state, compare:
- Bazel, rules, and configuration revisions that shape the action;
- declared source, generated, and tool inputs;
- arguments, action environment, declared outputs, and execution properties;
- target and execution platform consequences, toolchain selection, and any worker-image or host dependency not represented above;
- digest and protocol capabilities; and
- controlled output equivalence for the action classes you intend to share.
Treat an unexplained dimension as unknown, not as permission to merge namespaces for a better hit rate. Apply the compatibility matrix and bounded canary from 6.1.5 Safe Infrastructure Rollouts before widening readers or writers. Digest equality proves byte identity. It cannot prove that an underdeclared action was entitled to produce those bytes.
Decide: Linux CI and macOS developers run the same Java target. The source
files match, and both toolchains call their compiler javac. May they share the
action result?
Reveal
Not from those facts. Compare the concrete compiler inputs and digests, command and environment, selected target and execution platforms, declared outputs, and any ambient runtime dependencies. The target label and tool name are not the action identity. Sharing is justified only if every output-relevant difference is represented or a bounded normalization has an independently tested equivalence contract.
Path mapping is a bounded normalization
Output paths can contain configuration-derived segments even when an eligible
action would produce equivalent bytes across those configurations. In Bazel
9.1.0, --experimental_output_paths=strip enables first-party path mapping.
It is off by default and still marked highly experimental. Eligibility is per
action: Starlark actions opt in with the supports-path-mapping execution
requirement, and Bazel supplies support for selected built-in action classes.7,8
For an eligible action, Bazel coherently maps generated-artifact paths in the
structured command line and in execution staging to a configuration-neutral
form such as bazel-out/cfg/...; source paths are not rewritten. Bazel 9.1.0
does not apply this mapping to actions constrained to incompatible local
execution, and it falls back to the unmapped identity when generated inputs
would collide after mapping. The operator still has to prove that the tool does
not embed or otherwise depend on the original path. Debug information,
manifests, and eagerly constructed path strings are reasons an action may be
ineligible.8,9
Verify the optimization with two controlled configurations, an isolated disk or remote cache, and execution logs: establish a miss/write first, then show that only opted-in, non-colliding actions hit across the path-only change while their output digests remain equal. Also include a negative action whose output depends on the original path and confirm that it is excluded.
Path mapping does not erase different tool binaries, hidden environment, or platform-sensitive output semantics. Arbitrary backend-side key rewriting is a different implementation mechanism. It is not Bazel path mapping and must not be presented as Bazel behavior; it needs its own equivalence, versioning, invalidation, and rollback proof.
A shared-cache key is a cross-client equivalence claim about one action, not a target label. Declared input and tool bytes, command and environment, outputs, configuration-selected platform and toolchain consequences, execution properties, and digest capabilities must describe the same computation—and no hidden ambient input may change its result.
Keep Bazel's local output-tree action-cache state, the HTTP remote-cache
protocol, and REAPI ActionCache/CAS/ByteStream distinct. An ActionResult
describes outputs; CAS holds their bytes. Bazel 9.1.0 path mapping can normalize
configuration-derived paths for eligible actions, but it remains experimental,
does not make different tools equivalent, and is not permission for a backend
to rewrite keys.
Check your understanding · 2 questions
1.Two clients request the same target label. What must justify reusing one remote result?
Select one answer
2.Match each cache surface to the thing it supplies:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
Footnotes
-
Remote APIs — protocol contracts for caching and remote execution — canonical
Actiondigest, referencedCommandand input-rootDirectory, directtimeout,do_not_cache,salt, andplatformfields, plus the v2.2 platform-field transition ↩1 ↩2 -
Distributed Builds — cross-client reuse requires reproducible actions whose complete inputs determine the same outputs on every machine ↩1 ↩2
-
Bazel and action (non-) determinism — action identity as command, declared input hashes, environment/configuration, and tools, plus ambient causes of divergent outputs ↩
-
The Many Caches of Bazel — local output-tree action-cache state versus remote AC/CAS and the distinction between remote action digests and local
actionKeyfields ↩1 ↩2 -
Understanding Bazel remote caching —
ActionResultmetadata, output digests, and content bytes stored separately in CAS ↩ -
Hermeticity — isolation, source identity, hermetic tools, and host-state leaks that invalidate cross-machine reuse ↩
-
Command-Line Reference — Bazel 9.1.0 flag syntax, default-off status, experimental classification, and Starlark opt-in for output path mapping ↩
-
Towards Faster Cross-Platform Builds — path-derived cache misses, structured-command requirements, action eligibility, and the distinction from unsafe key scrubbing ↩1 ↩2
-
Bazel — core implementation, documentation, and regression corpus — Bazel 9.1.0 path-mapper eligibility, generated-path collision fallback, and integration-test evidence ↩