5.9.4 Incrementality Mechanism
recommendedAfter 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
SkyValuecompares 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.
- The source-file value changes, so the compile action's Skyframe node can no longer be accepted solely from dependency versions.
- 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.
- 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 - 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
| Mechanism | Question | Possible result |
|---|---|---|
| Skyframe dependency check | Did any direct dependency's value change since this node last evaluated? | Mark the dirty node clean without running its function. |
| Skyframe value comparison | Did rebuilding this node return a value equal to its previous value? | Keep its change version stable so parents may be pruned. |
| Action-cache lookup | Do 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.
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
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
Footnotes
-
Skyframe — bottom-up invalidation, demand-driven evaluation, change pruning, resurrection terminology, and the C++ example ↩1 ↩2 ↩3 ↩4 ↩5
-
Bazel upstream source — current dirty lifecycle, dependency-version checks, value equality, and
ActionExecutionValue.equalsbehavior (verified 18 July 2026) ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
Bazel Caching Explained (4-part series) — persistent action-cache state and the distinction between input/output fingerprints and executed actions ↩1 ↩2