fix(ci): scope the Next.js build cache sticky disk per branch (#6072)

* fix(ci): scope the Next.js build cache sticky disk per branch

The Turbopack persistent build cache disk was keyed on github.event_name
alone, so every open PR shared one mutable volume. A sticky disk mount
clones the last committed snapshot and commits back last-write-wins, so
each PR build restored a cache produced by a different branch.

Measured on the same staging commit, two runs minutes apart: the
single-writer push disk compiled in 9.3 min, the shared pull_request
disk in 14.0 min. Across 22 recent runs, push builds land at 3.5-11 min
and PR builds at 13.5-17.8 min.

Correctness matters more than the minutes here.
turbopackFileSystemCacheForBuild is beta and cross-commit restore is not
a documented-supported mode - vercel/next.js#87283 reports stale HTML
from a cache built at another commit, with no maintainer answer. Every
other cache layer in this repo already isolates by branch: GitHub's
cache cannot read sibling branches, and Blacksmith's cache product
branch-scopes by default. Only this key didn't.

Adds pre/post-build cache size reporting, because whether the cache was
warm is otherwise invisible - the disk mounts either way and turbo
buffers the build log. Adds ci-cache-cleanup.yml to delete a branch's
disk when its PR closes, so per-branch disks track open PRs instead of
accumulating at ~5 GB each.

Also corrects the 105s-cold/22s-warm comment: those were local numbers
and have never reproduced in CI.

* refactor(ci): trim the cache-key comments to the load-bearing facts

Keeps the mechanism (one mutable volume per key, clone-on-mount,
last-write-wins) and the two numbers, drops the restated reasoning. Both
report steps collapse to a single du, dropping a second full walk of a
~5 GB tree. Cleanup workflow loses a concurrency group a once-per-PR
delete never needed.
This commit is contained in:
Waleed
2026-07-29 17:27:55 -07:00
committed by GitHub
parent 161b21f86b
commit 998fd5a340
2 changed files with 58 additions and 6 deletions
+32
View File
@@ -0,0 +1,32 @@
name: CI Cache Cleanup
# test-build.yml keys the Next.js build cache sticky disk per branch, so every PR
# leaves a ~5 GB volume behind. Branches are short-lived; the disks aren't. Only
# the pull_request disks are reclaimed — the push disks belong to main/staging/dev
# and must stay warm.
on:
pull_request:
types: [closed]
permissions:
contents: read
jobs:
delete-nextjs-cache:
name: Delete Next.js build cache disk
# Sticky disks only exist on Blacksmith; the GitHub break-glass path uses
# actions/cache, which expires on its own.
if: vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
steps:
# Must stay byte-identical to the Mount Next.js build cache key in
# test-build.yml, or this deletes nothing and the disks accumulate.
# Non-blocking: PRs skipped by ci.yml's paths-ignore never made a disk.
- name: Delete sticky disk
uses: useblacksmith/stickydisk-delete@b41313d28b8647d72114c9ba3c96bb04061562b6 # v1
continue-on-error: true
with:
delete-key: ${{ github.repository }}-nextjs-cache-pull_request${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref }}
+26 -6
View File
@@ -265,14 +265,21 @@ jobs:
# Turbopack's persistent build cache (NEXT_TURBOPACK_BUILD_CACHE below)
# writes ~5 GB into .next/cache — a sticky disk mounts it in ~1s where an
# actions/cache round-trip would eat the warm-build win. Same event/fork
# namespacing as the other mounts; the GitHub fallback inside cache-mount
# still uses actions/cache with a run_id-suffixed key.
# actions/cache round-trip would eat the warm-build win.
#
# Keyed per branch, not just per event. A sticky disk is one mutable volume
# per key: mounting clones the last committed snapshot, job end commits back
# last-write-wins. An event-only key had every open PR restoring a cache
# built from a different branch — 14.0 min vs 9.3 min for the single-writer
# push disk on the same commit. Branch scoping also keeps us off
# cross-commit restore, which turbopackFileSystemCacheForBuild (beta) does
# not document as supported (vercel/next.js#87283: stale HTML from a cache
# built at another commit). ci-cache-cleanup.yml reclaims the disks.
- name: Mount Next.js build cache
uses: ./.github/actions/cache-mount
with:
provider: ${{ vars.CI_PROVIDER }}
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref || github.ref_name }}
path: ./apps/sim/.next/cache
# Running out of RAM kills the whole VM and surfaces only as "the runner
@@ -293,6 +300,13 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile --ignore-scripts
# The disk mounts successfully whether or not it carried anything and turbo
# buffers the build log, so cache warmth is otherwise unobservable — #5859
# shipped a cache that carried almost nothing and it took a PR to notice.
# Reported, never gated.
- name: Report Next.js cache size (pre-build)
run: du -sh apps/sim/.next/cache 2>/dev/null || echo 'cold — no cache restored'
- name: Build application
env:
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
@@ -304,7 +318,13 @@ jobs:
AWS_REGION: 'us-west-2'
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
TURBO_CACHE_DIR: .turbo
# Opt into Turbopack's persistent build cache (beta) for this CI
# check build only — measured 105s cold vs 22s warm locally.
# Opt into Turbopack's persistent build cache (beta) for this CI check
# build only. #5869's 105s-cold/22s-warm was measured locally and has
# never reproduced in CI (compile has ranged 3.5-17.8 min) — local
# numbers, not a CI target.
NEXT_TURBOPACK_BUILD_CACHE: '1'
run: bunx turbo run build --filter=sim
- name: Report Next.js cache size (post-build)
if: always()
run: du -sh apps/sim/.next/cache 2>/dev/null || echo 'no cache written'