From 3f7c7857def608fc801dde793cccf6940aaadf2e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 28 Aug 2026 02:05:32 -0700 Subject: [PATCH] improvement(ci): bound the local Turborepo cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turborepo's local cache eviction is opt-in until 3.0, so without cacheMaxAge and cacheMaxSize the filesystem cache grows forever. In CI each cache dir lives on a Blacksmith sticky disk that is 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. The build cache disk reached 206 GB over 43 days — roughly 4.8 GB/day of sediment — at ~$0.51/GB-month. Size is the real bound; age is hygiene. A cache hit only happens when a task's input hash is unchanged, which recurs within hours, not weeks, so nothing written days ago can ever be read again — the sibling PR-keyed disk does the same job in 30 GB. The cache still earns its keep: the app build hits ~17% of the time and a hit saves ~7 minutes, so this is a ceiling, not a removal. That distinguishes it from the Turbopack persistent cache, which was removed because it measured 3.2x SLOWER; this one is measurably faster, just unbounded. Set in turbo.json rather than per-step env vars so there is one source of truth and turbo validates it. Verified against the installed 2.9.14: both keys are in its schema, turbo parses them, and the task hash is byte-identical with and without them, so enabling eviction does not invalidate the existing cache. --- turbo.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/turbo.json b/turbo.json index bdb8730df0f..619997da6ee 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,23 @@ { "$schema": "https://v2-9-12.turborepo.dev/schema.json", "envMode": "loose", + // Local cache eviction is opt-in until Turborepo 3.0, so without these the + // filesystem cache grows forever. In CI each cache dir is a Blacksmith sticky + // disk that is mounted many times a day, so it never goes idle long enough for + // Blacksmith's own 7-day inactivity purge to fire, and one cache-missing app + // build writes a ~400 MB artifact: the build cache reached 206 GB in 43 days. + // + // Size is the real bound and age is hygiene. A hit only happens when a task's + // input hash is unchanged — a re-run, or a commit touching only unrelated + // workspaces — which recurs within hours, so nothing older than a day or two + // can ever be read again. Measured hit rate on the app build is ~17%, worth + // ~7 minutes each, so the cache earns its keep; it just needs a ceiling. + // 40 GB is ~100 app-sized artifacts against ~4.8 GB/day of real accumulation. + // + // Neither key participates in the task hash, so changing them does not + // invalidate the cache. + "cacheMaxAge": "7d", + "cacheMaxSize": "40GB", "tasks": { "transit": { "dependsOn": ["^transit"],