Why do my fonts look wrong in a HyperFrames render, and what is FONT_FETCH_FAILED?
A font that looks right in preview can fall back, change weight or fail the render outright. Embed the font with @font-face, understand what the compiler does with a named Google Font, and know why Arial and Segoe UI behave differently from Helvetica.
FONT_FETCH_FAILED
font_family_without_font_face
google_fonts_import
Fonts are where the preview-to-render gap shows up most, because a browser on your laptop quietly substitutes a font that a headless render on another machine will not.
Three different symptoms
- The wrong font appears. The render uses a fallback or a different cut than the preview.
- The render fails with
FONT_FETCH_FAILED. Seen on distributed and cloud renders. - A lint warning such as
font_family_without_font_faceorgoogle_fonts_import.
They have one root: the renderer has to resolve every font before the first frame, with no network surprises, so that the same input gives the same pixels.
Fix: embed the font
Ship the font file in the project and declare it:
@font-face {
font-family: "Brand Sans";
src: url("./assets/fonts/BrandSans-Regular.woff2") format("woff2");
font-weight: 400;
}
That removes the network, the machine and the Chrome version from the equation. It is also what HeyGen's own error guidance says to do when a font cannot be resolved: embed it with @font-face, or use a Google Fonts family.
The Google Fonts story, because it is confusing
There are three sources that appear to disagree, and it helps to know all three.
- According to the project's typography reference, the compiler can fetch a real Google Font at build time when you name a family without a matching
@font-face. On local renders that works, and it triggers thefont_family_without_font_facewarning.google_fonts_importis the sibling rule: it flags an@importof a Google Fonts URL, which is another way of depending on the network at render time. - On distributed and cloud (Lambda) renders, the same fetch is fail-closed, so a family that cannot be fetched is a hard error rather than a quiet fallback.
- One of the project's own skill references states the stricter rule: never name a font that has no file.
An issue filed in September 2026 points out that these do not agree with each other, and was closed on September 23. The practical reading is simple: for anything you care about, ship the file.
Why Arial and Segoe UI behave differently from Helvetica
A real example from the fix. After a change added a Google Fonts supplement fetch, distributed renders of any composition naming a non-Google family started failing with FONT_FETCH_FAILED. Checked against Google directly:
| Family | HTTP status from Google Fonts |
|---|---|
| Inter | 200 |
| Helvetica Neue | 200 |
| Helvetica | 200 |
| Arial | 400 |
| Segoe UI | 400 |
Google does not host Arial or Segoe UI, so it answers 400. A 400 is a deterministic answer: it is the same on every retry, so it cannot break reproducibility. The fix, merged May 19, 2026, treats any 4xx as "not served" and falls back, and keeps failing closed only for 5xx responses and network errors, where a retry might change the result.
A subtler version, fixed in September 2026
Rendered text could use a different Google font file than the preview did, because the renderer reconstructed a request by weight rather than using what the page actually linked. A fix merged on September 28, 2026 embeds the Google font the page linked instead of a wider cut. If you saw a weight mismatch or a different cut on an older version, upgrade.
Check it worked
npx hyperframes lint
npx hyperframes render --docker --output output.mp4
lint shows the font warnings. A Docker render pins the Chromium version, the font set and the FFmpeg encoder, so if the text matches in Docker, the font is resolving deterministically.
Fonts that live in the project

A GenMotion project is a folder you own, with fonts kept in its assets and declared in the composition, and the HyperFrames skills the agent follows say never to name a font that has no file. The preview and the exported MP4 are built from that one folder. Tell the agent which brand font to use and where the file is.
Related answers
Why does my HyperFrames render look different from the preview?
Preview and render run the same runtime, so a real difference has a specific cause: fonts, remote media, a cold-seek state problem, the wrong entry file, or a variable that never arrived. Here is how to find which.
Why is my HyperFrames render slow, and what actually speeds it up?
Separate a slow preview from a slow render, find the fixed 45 second tax, then work through source video, filters, encoding and resolution. Real fixes from HyperFrames' own performance guide.
What are the HyperFrames determinism rules, in one checklist?
Same composition, same video, every time, as long as nothing in a frame reads the clock, an unseeded random number or the network. The rules, the reason for each, and the lint codes that enforce them.
How to create a product launch video with HyperFrames
Build a 12 second, four scene product launch video from an empty folder with HyperFrames: the project layout, every file, the check and snapshot commands, a real render, and the mistakes the linter catches. Tested on HyperFrames 0.8.134.
Sources
- HyperFrames error codes: a font cannot be resolved
- hyperframes#957: treat 4xx from Google Fonts as deterministic, not as fail-closed
- hyperframes#4569: embed the Google font the page linked instead of a wider cut
- hyperframes#3583: Google Fonts and the lint rule disagree about named fonts
- HyperFrames: Deterministic Rendering
This answer as plain Markdown, for agents: /answers/hyperframes-fonts-wrong-in-render.md