Skip to content

fix(build): dependency-aware cache key so dependents rebuild on dep change#5425

Merged
adrians5j merged 5 commits into
nextfrom
claude/build-dep-aware-cache-key
Jul 17, 2026
Merged

fix(build): dependency-aware cache key so dependents rebuild on dep change#5425
adrians5j merged 5 commits into
nextfrom
claude/build-dep-aware-cache-key

Conversation

@adrians5j

@adrians5j adrians5j commented Jul 16, 2026

Copy link
Copy Markdown
Member

Problem

The build decides rebuild-vs-reuse per package by hashing only that package's own src (getPackageSourceHash) and comparing to the hash in meta.json. It ignores dependencies. So when package P changes its exports, any dependent Q whose own source is untouched stays a cache hit → never rebuilt, and its dist remains compiled against P's old API → dangling imports / stubs. Whack-a-mole after every merge.

--rebuild-dependents only patches this if run on the exact first build after the change (it anchors off transient cache misses, and a plain build records the new hash, erasing the signal).

Fix — dependency-aware effective key

key(pkg) = hash(own source hash + resolved third-party deps + sorted keys of workspace deps)

Computed once over the toposort (deps first), stored in build meta. Any transitive workspace dep change — or a resolved third-party dep change (parsed from yarn.lock, so floating ranges / lockfile updates count too) — flips a dependent's key → cache miss → rebuilds, on a plain yarn build, no flag.

Relies on the workspace dependency graph (package.json), which adio keeps in sync with real imports.

⚑ Behind an experimental flag — OFF by default

To merge safely and let it be validated locally first, the dep-aware key is opt-in:

  • default (flag off): build key = own-source hash — byte-identical to current behavior, so merging this forces no rebuild for anyone. --rebuild-dependents remains the mechanism for rebuilding dependents.
  • WEBINY_EXPERIMENTAL_DEP_AWARE_CACHE=true (or 1): the dependency-aware key above is used.

Try it locally:

WEBINY_EXPERIMENTAL_DEP_AWARE_CACHE=1 yarn build

Verified

  • flag on: editing @webiny/error → plain yarn build rebuilds 109 packages (error + 108 dependents); own-hash rebuilt 1, leaving 108 stale.
  • lockfile granularity: niche dep checksum bump → 67 keys; typescript (near-universal devDep) → 142.
  • flag off: getOwnHashes equals getPackageSourceHash (byte-identical → no forced rebuild); both modes run.
  • key compute ~1.1s (no measurable overhead).

Scope / follow-up

🤖 Generated with Claude Code

adrians5j and others added 2 commits July 16, 2026 17:13
…hange

The build's rebuild-vs-reuse decision hashed only a package's OWN source
(`getPackageSourceHash`) and compared to the stored hash. It ignored
dependencies — so when a package's exports changed, any dependent whose own
source was untouched stayed a cache hit and was never rebuilt, leaving its dist
compiled against the old dependency API (dangling imports / stubs). Whack-a-mole
after every merge.

Introduce an effective build key:

    key(pkg) = hash(own source hash + sorted keys of its workspace deps)

Computed once over the toposort (deps before dependents), stored in build meta.
Any transitive dependency change flips a dependent's key → it becomes a cache
miss and rebuilds — on a plain `yarn build`, no `--rebuild-dependents` needed.
Relies on the workspace dependency graph (package.json), which `adio` keeps in
sync with actual imports.

Verified: editing @webiny/error rebuilds it + its 108 dependents (was 1);
147 keys compute in ~1s (no measurable overhead).

`--rebuild-dependents` is now redundant but left in place; it (and the CI flag)
can be removed in a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The effective key hashed own source + workspace dep keys, and captured
third-party deps only via the package.json range (part of the own-source hash).
That missed the case where a range resolves to new code without any
package.json edit — a floating range picking up a release, or a `yarn.lock`
update. Dependents of that resolution weren't rebuilt.

Parse `yarn.lock` and fold each package's *resolved* third-party dependency
tokens (checksums) into its key. A lockfile change invalidates only the
packages that actually declare the changed dependency — plus their dependents —
not the whole repo.

Verified: bumping a niche dep's checksum flips only its declarer + dependents
(67/147); bumping `typescript` (a near-universal devDep) flips 142/147. Key
compute stays ~1.1s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adrians5j and others added 3 commits July 17, 2026 08:02
Make the dependency-aware effective key opt-in so it can merge without
affecting anyone by default, and be validated locally first.

- default (flag off): build key = own-source hash — byte-identical to the
  previous behavior, so merging forces no rebuild; `--rebuild-dependents`
  remains the mechanism for rebuilding dependents.
- WEBINY_EXPERIMENTAL_DEP_AWARE_CACHE=true: build key folds in transitive
  workspace dep keys + resolved third-party deps, so a plain `yarn build`
  rebuilds dependents of any changed package.

Verified: flag-off own hashes equal getPackageSourceHash (no forced rebuild);
both modes run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adrians5j
adrians5j merged commit 3b5f1a8 into next Jul 17, 2026
19 checks passed
@adrians5j
adrians5j deleted the claude/build-dep-aware-cache-key branch July 17, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant