Skip to main content
aspect cache diff lists the test targets affected by the current working tree by diffing it against your remote cache. It runs no tests. A single Bazel invocation with --experimental_remote_require_cached probes the cache: every cache hit means “unaffected”, and every miss is attributed to the dependent tests. Strictly a remote-cache operation — no remote build execution (RBE) is required.

When to use it

  • Debug cache misses. Find the exact set of cache-missed actions that explain why a CI run rebuilt or re-ran more than expected.
  • Validate CI cache coverage. Run it against a clean checkout to confirm your CI is uploading the baseline you expect — a populated cache should report Affected 0 of N.
  • Pre-flight a change. Before pushing, list the tests that are actually affected by your edits and pipe them into bazel test to run only what matters.
  • Drive selective CI. Use the lines or json output to gate downstream jobs on the affected target set.

How it works

  1. Enumerates the test targets in scope with tests(<patterns>).
  2. Runs one Bazel invocation with --experimental_remote_require_cached. Each action’s up-to-date check resolves against the remote cache; on a miss, execution is denied so the probe runs nothing.
  3. Reads the gRPC log of GetActionResult calls. A miss anywhere in a test’s transitive closure trips at that action — the frontier.
  4. Attributes the affected tests from the frontier (overreport) or only from missing test actions (precise).
Affected labels stream to stdout; progress, per-target reasons, and the summary go to stderr — so you can pipe stdout cleanly.

Prerequisites

  • A writable --remote_cache configured in .bazelrc (or via --bazel-flag). The task fails fast if no remote cache is configured.
  • A populated baseline: a cache hit is what makes a target “unaffected”, so the mainline’s action results must already be uploaded. Normally this happens as a byproduct of regular CI:
    Seed on a clean checkout (or a fresh --output_base) — on a warm output base Bazel finds everything locally cached, executes nothing, and uploads nothing. CI checkouts are cold, so this is automatic there.
  • The probe and the baseline must resolve the same Bazel flags, so they compute the same action digests. The probe reuses the same rc/config as aspect build / aspect test, so they line up by default. A flag that changes an action’s environment (for example --action_env=FOO) must be present on both sides or neither.

Modes

Pick a mode with --mode: Execution guarantee: no test ever runs in either mode. overreport executes nothing; precise executes only the missing non-test actions (it builds with bazel build, which cannot execute TestRunner). The lone exception is actions tagged no-remote, local, no-remote-exec, or no-cache — those bypass the remote path entirely, so they run locally and read as affected.

Output

By default, affected labels stream to stdout, one per line:
Pipe directly into Bazel:
Or hand the labels to any command with --exec:
For scripting, switch to JSON:
Progress, per-target reasons (including the cache-missed target that each test is “caused by”), and the final Affected X of N test target(s). summary go to stderr.

Configuration flags

Run aspect cache diff --help for the complete list of flags.

Examples

Scope to a sub-tree and run only the affected tests:
Use a CI config so the probe matches your CI baseline’s flags:
Narrow overreport noise to genuine input changes:
Drive a downstream job from JSON:

Soundness and limits

The result is bounded by your baseline’s cache coverage:
  • A hit means the target is unaffected.
  • A miss means the target is either changed or never-uploaded/evicted.
Against a fully-populated baseline this is exact. Against a sparse cache, overreport over-reports and precise narrows the result to genuine input changes. The probe runs with --nokeep_state_after_build --nouse_action_cache, which forces a fresh remote GetActionResult per action — so the command runs on the default output base with no separate or wiped base.