Skip to content

fix(engine): suppress font-load 404s by checking console location URL#313

Merged
jrusso1020 merged 1 commit intomainfrom
fix/browser-console-font-filter
Apr 18, 2026
Merged

fix(engine): suppress font-load 404s by checking console location URL#313
jrusso1020 merged 1 commit intomainfrom
fix/browser-console-font-filter

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

Problem

In headless render environments, every font-load 404 appears in the render output as [non-blocking] Failed to load resource: … noise — the exact symptom that PR #311 was targeting:

[Compiler] Injected deterministic @font-face rules for 2 requested font families
[non-blocking] Failed to load resource: the server responded with a status of 404 (Not Found)
[non-blocking] Failed to load resource: the server responded with a status of 404 (Not Found)

The existing filter in packages/engine/src/services/frameCapture.ts was supposed to suppress these (see the comment at line 153-154: "Suppress font-loading 404s entirely. These are expected…"), but it doesn't actually work:

const isFontLoadError =
  type === "error" &&
  text.startsWith("Failed to load resource") &&
  /fonts\.googleapis|fonts\.gstatic|\.woff2?(\b|$)/i.test(text); // ← only checks text

Chrome's console message text() for a failed resource is just "Failed to load resource: net::ERR_FAILED" or "Failed to load resource: the server responded with a status of 404 (Not Found)"the URL is not in the text, it's on msg.location().url. So the regex never matches and every font 404 falls through to the [non-blocking] prefix.

Reproducer

// Puppeteer console event for a 404'd Google Fonts <link>:
// msg.text()         → "Failed to load resource: net::ERR_FAILED"
// msg.location().url → "https://fonts.googleapis.com/css2?family=NonExistent:wght@400"

The old regex test against text returns false, so the event is logged. Verified locally with a minimal Puppeteer reproducer.

Fix

Extract the classifier into a testable isFontResourceError(type, text, locationUrl) function and match against both text and locationUrl. Also extend the extension set to .ttf and .otf.

This is a targeted fix for the same noise PR #311 was after, without changing any font-resolution behavior.

Why not the SYSTEM_FONTS approach in PR #311

PR #311 adds a ~120-entry SYSTEM_FONTS skip set to extractRequestedFontFamilies(). It silently shadows entries already in FONT_ALIASES:

  • arial → inter
  • helvetica, helvetica neue, helvetica bold → inter
  • courier, courier new → jetbrains-mono
  • segoe ui → roboto
  • arial black, futura → montserrat

With that PR applied, those aliases become dead code because extractRequestedFontFamilies skips the font before buildFontFaceCss ever runs. On render fleets (e.g. Dockerfile.testfonts-liberation/fonts-dejavu-core/fonts-noto-*, no Arial/Helvetica/SF Pro/Segoe UI/Tahoma installed), compositions that previously rendered with the aliased web font would shift to whatever fontconfig substitutes (Liberation Sans/Mono). That's a silent visual regression for existing fixtures such as packages/producer/tests/style-15-prod/src/compositions/hero_moment.html which uses "Helvetica Neue", Helvetica, Arial, sans-serif.

It also misidentifies the root cause: Courier New and Courier are already in FONT_ALIASES and never reach Google Fonts, so the 404s attributed to them in that PR's description aren't coming from Path 2 in buildFontFaceCss. They're the generic <link rel="stylesheet" href="fonts.googleapis.com/…"> tag (emitted by packages/core/src/generators/hyperframes.ts:104-107) failing in a sandbox, which the broken filter above was supposed to hide.

Testing

  • Added packages/engine/src/services/frameCapture.test.ts with 10 cases:
    • Google Fonts CSS / gstatic binaries / self-hosted woff2 / .ttf / .otf all suppressed via location.url
    • Images, scripts, videos NOT suppressed
    • URL-in-text (older Chrome format) still suppressed
    • Non-error log levels not suppressed
    • Case-insensitive URL matching
  • bun run --filter @hyperframes/engine test → 52/52 pass (5 files)
  • bunx oxlint / bunx oxfmt --check clean on changed files

Verified behavior

Before fix, reproducer shows:

[console.error] text="Failed to load resource: net::ERR_FAILED" — filter-matches=false
  (location.url: https://fonts.googleapis.com/css2?family=NonExistent:wght@400)

After fix, the same event matches the filter and is suppressed.

🤖 Generated with Claude Code

Chrome's "Failed to load resource" message text does not include the failing
URL — it's only on msg.location().url. The previous filter in frameCapture.ts
only checked msg.text(), so every font 404 (e.g. Google Fonts <link> tags
in sandboxed render environments) fell through to the "[non-blocking]"
prefix instead of being suppressed.

Extract the classifier into isFontResourceError() and match against both
text and location.url, and extend the extension match to .ttf/.otf. Adds
a unit test covering the URL-in-location, URL-in-text, and non-font cases.

This is a targeted fix for the render-output noise that PR #311 attempted
to address by adding a ~120-entry SYSTEM_FONTS skip list. That approach
silently shadowed existing FONT_ALIASES (arial→inter, helvetica→inter,
courier new→jetbrains-mono, segoe ui→roboto, etc.) and changed render
output on Linux fleets that don't have those fonts installed. Fixing the
console-log filter here suppresses the noise without changing any font
resolution behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jrusso1020 jrusso1020 merged commit 686e45d into main Apr 18, 2026
20 checks passed
@jrusso1020 jrusso1020 deleted the fix/browser-console-font-filter branch April 18, 2026 00:38
@jrusso1020 jrusso1020 mentioned this pull request Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants