perf(ci): collapse remaining per-PR sticky-disk minters to O(1) keys#109929
Conversation
The installation hit Blacksmith's backing-disk cap because sticky keys embedded PR numbers and content hashes, minting a new disk per PR and per input change until every mount 429-failed fleet-wide. node-deps (#109752) and ext-boundary (#109804) were already re-keyed; this converts the last two minters and audits the third suspect. Keys changed: - Vitest fs transform cache: `vitest-fs-v2-<pr-N|protected>-...` -> the one existing `vitest-fs-v2-protected-<os>-<arch>-node-<ver>` disk. Entries are content-hash keyed (vitest sha1 over id+content+NODE_ENV+version+env config), so cross-PR sharing is safe by construction: PRs now mount the protected snapshot directly (read-only, commit false) and the separate PR-only seed mount is deleted. Writers (non-PR shard writer + scheduled warm run) keep explicit commit true. Disk count: 1 + O(open PRs) -> 1. - Gradle: `gradle-v1-<task>-<pr-N|protected>-<hashFiles(deps)>` -> `gradle-v2-<task>`. Task scope stays (light ktlint must not seed heavy build lanes); PR number and dependency hash leave the key. The dependency hash moved to an in-job fingerprint marker that makes the non-PR writer rebuild its snapshot cold when inputs change, bounding disk growth; PR mounts are read-only and safely reuse stale snapshots because Gradle caches are content-addressed. Disk count: O(tasks x PRs x dependency bumps) -> O(tasks) (7 today). - Docker builder (useblacksmith/setup-docker-builder): audited, out of scope. The action owns its key internally and always uses the repo name only (src/setup_builder.ts getStickyDisk), so it is already one disk per installation repo and cannot mint per-PR disks. PR warm-path behavior changes: - Vitest: PRs lose only PR-local warm entries for files the PR itself changed (previously persisted on the pr-N disk between pushes); changed files re-transform once per push, unchanged files still hit the protected snapshot. PRs touching transform inputs (lockfile/tsconfig/package.json) run cold per push since the generation wipe is now local to the discarded clone. - Gradle: dependency-bump PRs get warmer (stale-but-valid protected snapshot instead of a cold fresh key); other PRs are unchanged. Deleted `.github/workflows/pr-cache-cleanup.yml`: it existed for the per-PR cache layer (#109425) and only deleted GitHub actions/cache archives, which GitHub's own LRU/TTL eviction already handles for the remaining fork/Windows per-PR archive paths. Guards: pinned the O(1) vitest key + non-PR commit gate, added a Gradle per-task key/writer/fingerprint guard, added a repo-wide scan asserting no useblacksmith/stickydisk key ever contains github.event.pull_request.number or hashFiles(), and replaced the cleanup-workflow assertions with a stays-deleted check.
|
Codex review: needs real behavior proof before merge. Reviewed July 17, 2026, 6:42 AM ET / 10:42 UTC. Summary PR surface: Tests +60, Config +1. Total +61 across 4 files. Reproducibility: yes. at source level: the previous sticky keys visibly include pull-request or content-hash dimensions, and the PR provides a concrete fleet-wide 429 symptom and run context. This review did not independently repeat the installation-cap failure. Review metrics: 2 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Keep the fixed-cardinality Vitest and Gradle sticky-disk design, but restore the closed-PR GitHub Actions cache cleanup workflow and retain a guard proving it remains present while PR-scoped Do we have a high-confidence way to reproduce the issue? Yes at source level: the previous sticky keys visibly include pull-request or content-hash dimensions, and the PR provides a concrete fleet-wide 429 symptom and run context. This review did not independently repeat the installation-cap failure. Is this the best way to solve the issue? No as submitted: the fixed-cardinality sticky keys are the right bounded solution, but deleting an independent GitHub Actions cache cleanup path is unnecessary and should be reverted. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c45f299cea4d. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Tests +60, Config +1. Total +61 across 4 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Merged via squash.
|
…penclaw#109929) The installation hit Blacksmith's backing-disk cap because sticky keys embedded PR numbers and content hashes, minting a new disk per PR and per input change until every mount 429-failed fleet-wide. node-deps (openclaw#109752) and ext-boundary (openclaw#109804) were already re-keyed; this converts the last two minters and audits the third suspect. Keys changed: - Vitest fs transform cache: `vitest-fs-v2-<pr-N|protected>-...` -> the one existing `vitest-fs-v2-protected-<os>-<arch>-node-<ver>` disk. Entries are content-hash keyed (vitest sha1 over id+content+NODE_ENV+version+env config), so cross-PR sharing is safe by construction: PRs now mount the protected snapshot directly (read-only, commit false) and the separate PR-only seed mount is deleted. Writers (non-PR shard writer + scheduled warm run) keep explicit commit true. Disk count: 1 + O(open PRs) -> 1. - Gradle: `gradle-v1-<task>-<pr-N|protected>-<hashFiles(deps)>` -> `gradle-v2-<task>`. Task scope stays (light ktlint must not seed heavy build lanes); PR number and dependency hash leave the key. The dependency hash moved to an in-job fingerprint marker that makes the non-PR writer rebuild its snapshot cold when inputs change, bounding disk growth; PR mounts are read-only and safely reuse stale snapshots because Gradle caches are content-addressed. Disk count: O(tasks x PRs x dependency bumps) -> O(tasks) (7 today). - Docker builder (useblacksmith/setup-docker-builder): audited, out of scope. The action owns its key internally and always uses the repo name only (src/setup_builder.ts getStickyDisk), so it is already one disk per installation repo and cannot mint per-PR disks. PR warm-path behavior changes: - Vitest: PRs lose only PR-local warm entries for files the PR itself changed (previously persisted on the pr-N disk between pushes); changed files re-transform once per push, unchanged files still hit the protected snapshot. PRs touching transform inputs (lockfile/tsconfig/package.json) run cold per push since the generation wipe is now local to the discarded clone. - Gradle: dependency-bump PRs get warmer (stale-but-valid protected snapshot instead of a cold fresh key); other PRs are unchanged. Deleted `.github/workflows/pr-cache-cleanup.yml`: it existed for the per-PR cache layer (openclaw#109425) and only deleted GitHub actions/cache archives, which GitHub's own LRU/TTL eviction already handles for the remaining fork/Windows per-PR archive paths. Guards: pinned the O(1) vitest key + non-PR commit gate, added a Gradle per-task key/writer/fingerprint guard, added a repo-wide scan asserting no useblacksmith/stickydisk key ever contains github.event.pull_request.number or hashFiles(), and replaced the cleanup-workflow assertions with a stays-deleted check.
What Problem This Solves
The Blacksmith installation is at its 1000-backing-disk cap and every sticky mount currently 429s fleet-wide — the reason the landed warm-path levers (#109752 node-deps, #109804 ext-boundary) cannot produce a single warm hit. The cap was reached because sticky keys embedded PR numbers and content hashes, minting a new disk per PR and per input change. Two minters remained after the earlier re-keys.
Why This Change Was Made
vitest-fs-v2-<pr-N|protected>-…collapses to the one existing protected disk. Cache entries are content-hash keyed (sha1 over id+content+NODE_ENV+version+config), so cross-PR sharing is safe by construction — PRs now mount the protected snapshot read-only (commit: "false"), the separate PR-only seed mount is deleted, and writers (non-PR shard writer + scheduled warm run) keep explicitcommit: true. The PR-close cleanup workflow (pr-cache-cleanup.yml) is deleted with the per-PR disks it existed to reap. Trade-off named: a PR loses warm entries only for files it changed itself, which re-transform once per push either way. Disk count: 1 + O(open PRs) → 1.gradle-v1-<task>-<pr-N|hashFiles>→gradle-v2-<task>. Task scope stays (a light ktlint lane must not seed the heavy build lanes); the dependency hash moves into an in-job fingerprint marker that makes the non-PR writer rebuild cold on input changes; PR mounts are read-only and safely reuse stale snapshots because Gradle caches are content-addressed. Disk count: O(tasks × PRs × dependency bumps) → O(tasks), 7 today.useblacksmith/setup-docker-builderkeys internally by repo name only (src/setup_builder.tsgetStickyDisk) and cannot mint per-PR disks.github.event.pull_request.numberorhashFiles(), single-writer/read-only invariants pinned, and a stays-deleted check replaces the cleanup-workflow assertions.User Impact
None at runtime — CI-only. This stops the disk-cap regrowth so the operator purge is durable; combined with the purge it unlocks the warm paths of every sticky lever.
Evidence
node scripts/run-vitest.mjs test/scripts/ci-workflow-guards.test.ts— 80 passed, 1 skipped (rerun green after rebase onto current main).pnpm check:test-typesclean;actionlintclean;git diff --checkclean.