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_digest addresses the root Directory of 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_digest addresses a Command whose 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, Action also directly contains timeout, do_not_cache, salt, and platform. Their serialized values are part of the action digest. A true do_not_cache prevents caching in addition to participating in the message identity. Direct Action.platform was added in REAPI v2.2; the older Command.platform field remains deprecated for compatibility, and the protocol tells clients to set both while servers prefer the direct Action field.1
  • Build configuration, target platform, execution platform, and toolchain selection matter when they change the referenced input root or Command, or any direct Action field. 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.

SurfaceWhat the key findsWhat it does not establish
Bazel's local action-cache stateMetadata used to decide whether outputs already in this output tree remain validA shareable remote protocol object or proof that another client produced the result
HTTP remote cacheAn HTTP cache entry under Bazel's action-cache namespace, plus separately addressed content blobsThat REAPI protobuf services or remote execution participated
REAPI ActionCacheAn ActionResult addressed by the digest of an Action; that Action refers to a Command and an input rootThe 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:

  1. Bazel, rules, and configuration revisions that shape the action;
  2. declared source, generated, and tool inputs;
  3. arguments, action environment, declared outputs, and execution properties;
  4. target and execution platform consequences, toolchain selection, and any worker-image or host dependency not represented above;
  5. digest and protocol capabilities; and
  6. 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.

think

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.

key takeaway

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

Answers
Bazel local action cache
HTTP remote cache
REAPI ActionCache
REAPI CAS
0 of 2 answered

Footnotes

  1. Remote APIs — protocol contracts for caching and remote execution — canonical Action digest, referenced Command and input-root Directory, direct timeout, do_not_cache, salt, and platform fields, plus the v2.2 platform-field transition 1 2

  2. Distributed Builds — cross-client reuse requires reproducible actions whose complete inputs determine the same outputs on every machine 1 2

  3. Bazel and action (non-) determinism — action identity as command, declared input hashes, environment/configuration, and tools, plus ambient causes of divergent outputs

  4. The Many Caches of Bazel — local output-tree action-cache state versus remote AC/CAS and the distinction between remote action digests and local actionKey fields 1 2

  5. Understanding Bazel remote cachingActionResult metadata, output digests, and content bytes stored separately in CAS

  6. Hermeticity — isolation, source identity, hermetic tools, and host-state leaks that invalidate cross-machine reuse

  7. Command-Line Reference — Bazel 9.1.0 flag syntax, default-off status, experimental classification, and Starlark opt-in for output path mapping

  8. Towards Faster Cross-Platform Builds — path-derived cache misses, structured-command requirements, action eligibility, and the distinction from unsafe key scrubbing 1 2

  9. Bazel — core implementation, documentation, and regression corpus — Bazel 9.1.0 path-mapper eligibility, generated-path collision fallback, and integration-test evidence