improvement(ci): bound the local Turborepo cache
Summary
Turborepo's local cache eviction is opt-in until 3.0, so without cacheMaxAge/cacheMaxSize the filesystem cache grows forever. In CI each cache dir lives on a Blacksmith sticky disk that's mounted many times a day, so it never idles long enough for Blacksmith's own 7-day inactivity purge to fire — and one cache-missing app build writes a ~400 MB artifact.
Result: sim-turbo-cache-build-push reached 206 GB over 43 days (~4.8 GB/day of sediment) at ~$0.51/GB-month. The sibling build-pull_request key does the same job in 30 GB.
Size is the real bound; age is hygiene. A cache hit only happens when a task's input hash is unchanged — a re-run, or a commit touching only unrelated workspaces — and that recurs within hours, not weeks. Nothing written days ago can ever be read again.
This is a ceiling, not a removal. The app build hits >>> FULL TURBO ~17% of the time and a hit saves ~7 minutes, so the cache clearly earns its keep. That's what distinguishes it from the Turbopack persistent cache removed earlier, which was dropped because it measured 3.2× slower — this one is measurably faster, just unbounded.
Set in turbo.json rather than per-step env vars so there's one source of truth and turbo validates it.
Verification
Against the installed turbo 2.9.14 (not assumed — run):
- Both keys are present in its schema, and in the pinned
$schemaURL, so no schema bump is needed. turbo run --dry=jsonparses the config with no unknown-key error.- The task hash is byte-identical with and without the keys (
94bbd3189c137ec2both ways), so enabling eviction does not invalidate the existing cache. - The pre-commit biome hook preserves the JSONC comments.
Expected: ~246 GB → ~75 GB across the four turbo keys, roughly $125/mo → $38/mo, with no loss of realized hits (all observed hits were same-day).
Notes for the reviewer
- Eviction runs on a detached thread at the start of each
turbo run, so the existing 206 GB drains over several runs rather than in one. It can be reclaimed immediately with a one-shot sticky-disk delete if we'd rather not wait — that costs exactly one cold build, which 83% of pushes already pay. - Pre-existing wart, deliberately not touched here:
TURBO_CACHE_DIR: .turbois set on the build and test steps but not onturbo run type-check, so that job's cache splits across.turbo/and.turbo/cache/. Each invocation evicts its own dir so both are now bounded, but unifying them naively would strand the existing cache in the dir eviction no longer looks at.
Type of Change
- Improvement
Testing
Verified as above against the installed turbo binary. bun run lint and all 38 check:audits pass.
Checklist
- Code follows project style guidelines
- Self-reviewed my changes
- Tests added/updated and passing
- No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)