From da09428af19075d80f33aeb4ca575ba1efd4490b Mon Sep 17 00:00:00 2001 From: miga-heygen Date: Thu, 3 Sep 2026 04:00:37 +0000 Subject: [PATCH] fix(producer): degrade gracefully when font cache directory is unwritable (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(producer): degrade gracefully when font cache directory is unwritable When the font cache root (~/.cache/hyperframes/fonts/) cannot be created (EPERM on read-only filesystems, restricted home directories, etc.), the render aborts with a raw mkdir error. The cache is an optimization, not a requirement — a missing cache should mean slower first renders, not broken renders. Fall back to a temporary directory under os.tmpdir() when the configured cache root fails, so Google Fonts downloads still proceed. The fallback cache is per-process and not persistent across renders, but the render completes. Fixes #3412. * fix: use mkdtempSync for font cache fallback, restore warning Rames Jusso's review caught a regression in the force-push: the predictable tmpdir path is unsafe (symlink attacks in world-writable dirs), and the warning log was dropped. Restore the mkdtempSync pattern matching lambdaFontCacheRoot, add a CLI hint per Miguel's request, and reuse the ephemeral root across calls. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: emit font cache fallback warning once per run, not per font Gate the warning on whether this is the first fallback activation. The ??= already suppresses repeat mkdtempSync, but the warn fired for every font family in the composition. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../src/services/deterministicFonts.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/producer/src/services/deterministicFonts.ts b/packages/producer/src/services/deterministicFonts.ts index 7b99aad6e..3f537a791 100644 --- a/packages/producer/src/services/deterministicFonts.ts +++ b/packages/producer/src/services/deterministicFonts.ts @@ -746,10 +746,27 @@ function fontSlug(familyName: string): string { .replace(/^-|-$/g, ""); } +let ephemeralFontCacheRoot: string | undefined; + function fontCacheDir(slug: string): string { const dir = join(resolveFontCacheRoot(), slug); if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); + try { + mkdirSync(dir, { recursive: true }); + } catch { + const firstFallback = ephemeralFontCacheRoot === undefined; + ephemeralFontCacheRoot ??= mkdtempSync(join(tmpdir(), "hyperframes-fonts-")); + const fallback = join(ephemeralFontCacheRoot, slug); + mkdirSync(fallback, { recursive: true }); + if (firstFallback) { + defaultLogger.warn( + `Font cache directory is unwritable (${dir}). ` + + `Using temporary fallback — fonts will re-download each run. ` + + `Fix with: chmod 755 ${resolveFontCacheRoot()}`, + ); + } + return fallback; + } } return dir; }