5.9.4 Incrementality Mechanism

recommended

After an input changes, Skyframe does not immediately recompute every node above it. It marks the reverse dependency region potentially stale, then checks that region only as demanded by the new build. A dirty node is either verified clean from its dependencies or rebuilt. If rebuilding produces a value equal to the previous one, the change stops propagating. This is bottom-up invalidation plus change pruning.1

The distinction matters in incremental-build diagnosis. Dirty means “must be checked before reuse,” not “its function will run,” and certainly not “an action will execute.”

Dirty is a conservative state

Suppose the retained graph contains this dependency chain:

source state <- compile action <- object artifact <- link action <- binary

The arrows point from a consumer to a dependency, as in the Skyframe graph from 5.9.1 Skyframe Data Model. When filesystem checking reports that the source state changed, bottom-up invalidation follows reverse edges and marks its transitive dependents dirty. Bazel uses this strategy rather than a top-down scan of the newly requested graph. 5.9.5 Filesystem Watching explains how it can learn which filesystem leaves may have changed.1

This pass is deliberately conservative. The evaluator knows that an old dependency may no longer justify a cached value, but has not proved which ancestors' values differ. Nor must it eagerly visit every dirty node. A node outside the current request can remain dirty until a later evaluation asks for it.1,2

For a requested dirty node, Skyframe checks the direct dependencies recorded by its previous evaluation. If all of them still have versions no newer than the node's last evaluation, the node becomes verified clean: its old value and dependency edges remain valid, so its SkyFunction need not run. If a direct dependency really changed, the node needs rebuilding through the request and restart protocol in 5.9.2 Evaluation Model.2

Keep three outcomes separate:

  • dirty: the retained result is not yet trusted for this evaluation.
  • verified clean: dependency checking proved it reusable without recomputing the node.
  • rebuilt but unchanged: the function ran, yet its new SkyValue compares equal to the previous value.

The first two are evaluator lifecycle states. The third is the equality result that enables change pruning. It is not another Bazel phase or cache.

Change pruning uses value equality

When a rebuilt node returns a value, current Skyframe compares it with the previous value using equals, unless the value explicitly opts out of comparison. Changed dependency edges alone do not force the value to count as changed. If the values compare equal, Skyframe preserves the old value and does not advance that node's change version.2

Its dirty parents can then check the node and see no change since their last evaluation. If their other dependencies are also unchanged, those parents are marked clean without invoking their functions. Official documentation calls these previously invalidated dependents “resurrected.” The useful operational meaning is narrower: a node that is requested and whose dependencies prove unchanged can reuse its retained value without recomputation. It does not mean the whole invalidated reverse closure is eagerly made clean, or that an evicted value is recovered from storage.1,2

changed leaf -> rebuild A -> A's value is equal
                              |
                              +-> requested parent B checks A's version
                                  and becomes clean without rebuilding

Equality is part of each SkyValue implementation's correctness contract. A false “equal” result could hide a real change. An intentionally conservative comparison can merely prune less work. Current Bazel has values that opt out of value-based pruning, and some equality implementations choose conservative false negatives to avoid expensive comparisons.2

The C++ comment example crosses two layers

The official reference gives a memorable example: change a C++ comment, obtain the same object file, and avoid linking again.1 Read this as a conditional trace, not a promise that every comment edit produces identical bytes.

  1. The source-file value changes, so the compile action's Skyframe node can no longer be accepted solely from dependency versions.
  2. Because the source digest is an action input, the compile action ordinarily does not get an action-cache hit for the old invocation. Cache-key reuse is the separate mechanism introduced in 2.4.3 What Makes a Cache Hit.
  3. The compiler runs. If its resulting object metadata—most importantly the output content digest represented in ActionExecutionValue—equals the previous result, that Skyframe value compares equal.2,3
  4. The link action's dirty Skyframe node observes that its object dependency did not acquire a newer change version. It can be verified clean, so the linker action need not even reach its own action-cache lookup.

Thus “identical .o skips relinking” can be genuine Skyframe change pruning. It is not the action cache ignoring a changed source. The compile action may execute and still return an equal ActionExecutionValue. Equality prevents the link node from being recomputed.

The premise is toolchain- and flag-dependent. Editing comment text without changing line structure may disappear during preprocessing, but debug information, line-sensitive macros such as __LINE__, embedded source, coverage instrumentation, or other compiler behavior can change object bytes. If the object digest changes, pruning does not apply and the link action proceeds to its normal execution/cache decision. Verify this with the actual toolchain before using it as a performance expectation.

Keep pruning separate from action-cache reuse

MechanismQuestionPossible result
Skyframe dependency checkDid any direct dependency's value change since this node last evaluated?Mark the dirty node clean without running its function.
Skyframe value comparisonDid rebuilding this node return a value equal to its previous value?Keep its change version stable so parents may be pruned.
Action-cache lookupDo the action definition and declared input state match a stored result?Reconstruct outputs/result without executing the tool.

The in-memory Skyframe graph belongs to the long-lived Bazel server. An action cache can survive different boundaries and can supply results when Skyframe must reconstruct an action-execution value. Conversely, change pruning can avoid asking a parent action function to perform a cache lookup at all. Remote AC/CAS storage in 6.2.1 How Remote Cache Keys Work extends action-result reuse across machines under a distinct cross-client identity. It does not replace dirty checking and value-version propagation.3

For “why did this rebuild?” investigations, identify the outcome precisely. Was a node verified clean? Was it recomputed to an equal value? Did an action function run but receive a cache hit? Or did the tool process execute? Calling all four outcomes “cached” hides the mechanism that needs diagnosis.

key takeaway

Bottom-up invalidation marks the reverse transitive closure of changed inputs potentially stale. It does not prove every dirty node changed or must be recomputed. On demand, unchanged dependency versions can make a dirty node verified clean. When a rebuilt node returns a value equal to its old value, Skyframe keeps its change version stable so requested parents can be marked clean without rebuilding—change pruning, historically called “resurrection.”

The C++ comment example is conditional and spans two layers: the changed source normally prevents reuse of the old compile action result, but an executed compiler can still produce an equal ActionExecutionValue. Skyframe can then avoid recomputing the link node. Action-cache hits and Skyframe change pruning are complementary, not interchangeable.

Check your understanding · 4 questions

1.What does it mean when a retained Skyframe node is dirty?

Select one answer

2.Match each incremental-build outcome to the mechanism that produced it:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
A dirty node reuses its value without running its SkyFunction
A rebuilt node returns a value equal to its prior value
A tool does not execute because a stored action result matches

3.A comment edit changes a C++ source digest, the compiler runs, and the resulting object file has the same digest as before. Why can linking be skipped?

Select one answer

4.True or false: interpret these claims about change pruning.

Choose True or False for each sentence

Every dirty node in the reverse transitive closure is eagerly evaluated.
A value that opts out of equality-based comparison may reduce pruning without making the build incorrect.
Every C++ comment edit is guaranteed to produce an identical object file.
An unrequested dirty node may remain dirty after the current evaluation.
0 of 4 answered

Footnotes

  1. Skyframe — bottom-up invalidation, demand-driven evaluation, change pruning, resurrection terminology, and the C++ example 1 2 3 4 5

  2. Bazel upstream source — current dirty lifecycle, dependency-version checks, value equality, and ActionExecutionValue.equals behavior (verified 18 July 2026) 1 2 3 4 5 6

  3. Bazel Caching Explained (4-part series) — persistent action-cache state and the distinction between input/output fingerprints and executed actions 1 2