Skip to content

A rejected window.__hf.buildReady hold is ignored: check passes, render exits 0 (also with --no-best-effort), nothing is logged (0.8.157) #5603

Closed

Description

@tymoshenko-dmytro

The three.js adapter docs recommend registering a promise on window.__hf.buildReady for setup the
runtime cannot see (building meshes, compiling shaders, baking data). When that promise rejects, the
runtime treats the hold as released: the render runs every frame, writes the file, reports "Render
complete" and exits 0. The rejection reason is not logged anywhere. snapshot saves frames and check
reports 0 errors.

In a real three.js film the scene was never built, so every frame is empty or broken, and a pipeline that
trusts the exit code ships it. We hit this with WebGPU: with --no-browser-gpu three silently falls back
to WebGL 2, our build check rejects the hold, and the render still exits 0 with a blank 42 s film.
(data-requires-webgpu on the root does fail the render, exit 1 - but only for that one cause.)

Minimal repro

index.html (no dependencies):

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <script>
      window.__hf = window.__hf || {}; window.__hf.buildReady = window.__hf.buildReady || {};
      window.__hf.buildReady.scene = new Promise((res, rej) => { window.__sceneReady = res; window.__sceneFail = rej; });
    </script>
    <style>
      html, body { margin: 0; width: 1920px; height: 1080px; background: #000; }
      #c { position: absolute; inset: 0; width: 1920px; height: 1080px; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-start="0" data-duration="2"
         data-width="1920" data-height="1080" data-no-timeline>
      <canvas id="c" width="1920" height="1080"></canvas>
    </div>
    <script>
      const ctx = document.getElementById("c").getContext("2d");
      // the build fails (here: bad baked data); the hold rejects with a reason
      try { JSON.parse("{not json"); window.__sceneReady(); }
      catch (err) { window.__sceneFail(new Error("build failed: " + err.message)); }
      addEventListener("hf-seek", (e) => {
        ctx.fillStyle = "#e0893c"; ctx.fillRect(0, 0, 1920, 1080);
        ctx.fillStyle = "#000"; ctx.font = "120px sans-serif"; ctx.fillText("t = " + e.detail.time.toFixed(2), 200, 600);
      });
    </script>
  </body>
</html>
npx hyperframes@0.8.157 check                          # 0 errors, 0 warnings, exit 0
npx hyperframes@0.8.157 render --no-best-effort -o out.mp4
echo $?                                                # 0, "Render complete", 2.0 s video

The log contains no line with "build failed", "reject" or "buildReady".

(If the build fails because an asset 404s, check does report http_error, but render still exits 0.
The repro above uses a failure with no network error, which is the common case: a parse error, a missing
GPU feature, a shader that does not compile, a font that does not load.)

Expected

A rejected build hold fails render and snapshot with a non-zero exit and the rejection reason in the
output (at least under --no-best-effort; arguably always - there is no useful output from a composition
that says its own build failed). check could report it as an error too, since it loads the page.

Environment

hyperframes 0.8.157 and 0.8.156 · macOS 26 (Darwin 25.6), Apple M1 Max · Node 26.9.0 ·
chrome-headless-shell 152.0.7977.30 · hardware GPU and --no-browser-gpu both affected.

Workaround we use

Wrap the seek handler and paint a loud colour when the build failed, then check rendered frames for it:
if (!built) { clear to magenta; return; }.

Activity

  1. added a commit that references this issue on Oct 17, 2026
    c823bf8
  2. added
    triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.
    on Oct 23, 2026
  3. miguel-heygen commented on Oct 24, 2026

    @miguel-heygen
    Collaborator

    Thanks for the minimal repro, it made this quick to confirm.

    Reproduced on 0.8.193 (Linux, your index.html unchanged): check reports 0 errors and passes, and render --no-best-effort exits 0 with a 2 s video. Nothing in the render log mentions the rejection. The cause is that the runtime treats a rejected window.__hf.buildReady promise as settled and drops the reason.

    #5746 fixes this: a rejected hold fails render, snapshot and check, and the message names the key and the rejection reason. It's approved and waiting only on commit signing. Until it ships, your magenta-frame check is a reasonable workaround.

  4. added 4 commits that reference this issue on Oct 25, 2026
    1cc7082
    81be532
    ef38000
    6f87c1c
  5. added a commit that references this issue on Nov 5, 2026
    071ec1f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions