fix(cli): preload render.js once in renderLocal test suite (Windows CI)#659
Conversation
…dows CI
The first dynamic `await import("./render.js")` cold-load takes >5 s on
Windows runners — long enough to blow vitest's default 5 s timeout in
whichever test ran it first. Subsequent imports are <10 ms because the
module is now cached, so only test #1 ever times out.
The downstream failure is more subtle: when test #1 times out, vitest
moves on, but its leaked async function eventually hits the synchronous
`producer.createRenderJob(...)` line and pushes a stale config to
`producerState.createdJobs`. That push lands AFTER test #2's `beforeEach`
clears the array, so test #2's `createdJobs[0]` is the leaked test #1
entry instead of its own. That's why test #2 saw `browserGpuMode: 'software'`
when it expected `'auto'`.
Hoist the import into `beforeAll` (matching the pattern the existing
`parseVariablesArg` and `validateVariablesAgainstProject` describe blocks
in this file already use). Cold-load happens once outside any test's
timeout window, every test stays fast, no leaked promise can corrupt
state.
Failing run: https://github.com/heygen-com/hyperframes/actions/runs/25470257972/job/74732502915
Started failing on main with the merge of #642 (auto-detect-browser-gpu),
which added the "forwards browserGpuMode='auto'" test as test #2.
miguel-heygen
left a comment
There was a problem hiding this comment.
Clean fix, well-investigated root cause. The before/after is straightforward: 10 per-test dynamic imports collapse into one beforeAll, matching the pattern parseVariablesArg and validateVariablesAgainstProject already use in this same file. The resolveBrowserGpuForCli test correctly drops async since it no longer awaits an import.
All CI green, including the Windows render and test jobs that were previously failing.
No issues found.
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: LGTM — recommend approval (deferring the stamp to a human). Tightly-scoped fix for a real Windows CI flake, with a diagnosis that goes well past "tests are slow" into the actual leaked-promise / state-corruption mechanism. The fix matches the beforeAll-hoist pattern already used by the parseVariablesArg and validateVariablesAgainstProject describe blocks lower in the same file, so it's consistent with existing convention rather than introducing a new one. CI is green on Render on windows-latest (4m35s) and Tests on windows-latest (4m4s) — i.e. the exact jobs that were red on main post-#642.
Praise
- The PR description accurately separates the direct failure (test #1 timeout) from the downstream contamination of test #2 via the leaked
createRenderJobpush landing after the nextbeforeEachclear. That second-order analysis is what made #642 look like a flaky GPU test rather than a test-infrastructure problem; calling it out plainly in both the description and the inline comment will save the next person. - The
// Pre-resolve once. …block comment is the right size: explains the Windows-specific cause, the shifting-index failure mode, and whybeforeAllis the cure. Future readers won't be tempted to "simplify" it back to per-test imports. - One file, +14/-12, no drive-by refactors. Good discipline.
Nits
nit—packages/cli/src/commands/render.test.ts:34-37— the typedlet renderLocal/let resolveBrowserGpuForClideclarations are now duplicated for a third describe block in this file (the other two use the same pattern). If a fourth shows up it'd be worth a smallloadRenderModule()helper at module scope, but not blocking and arguably premature today.nit—packages/cli/src/commands/render.test.ts:123— convertingit("resolves browser GPU from CLI flags…", () => …)fromasyncto sync is correct (no awaits left in the body) and a nice touch, but worth flagging in the PR body since it's the one behavioural change beyond the hoist.
— Vai
vanceingalls
left a comment
There was a problem hiding this comment.
Approve. (Upgrading my prior LGTM-as-comment to a stamp now.)
Targeted fix for the Windows CI red — hoists the cold await import("./render.js") from every it into beforeAll, removing the cold-load spike that blew vitest's 5s timeout. The real second-order failure was the leaked late createRenderJob push landing after the next beforeEach cleared — corrupting test #2's createdJobs[0]. Inline comment captures this for future readers.
Pattern already used twice elsewhere in the same file. Single-file +14/-12, no scope creep. The previously-red Render on windows-latest job is green on this branch. Magi's approval converges.
— Vai
What
Hoist the dynamic
await import("./render.js")out of every test inrenderLocal browser GPU configand into abeforeAll. Same pattern theparseVariablesArgandvalidateVariablesAgainstProjectdescribe blocksin this file already use.
Why
The Windows render verification workflow has been red on
mainsince themerge of #642 (auto-detect-browser-gpu) — most recent failure on main:
https://github.com/heygen-com/hyperframes/actions/runs/25470257972/job/74732502915.
The first dynamic
await import("./render.js")in this file cold-loads inThe downstream failure was more subtle. When test #1 timed out, vitest
moved on, but its leaked async function eventually hit the synchronous
producer.createRenderJob(...)line and pushed a stale config toproducerState.createdJobs. That push landed AFTER test #2'sbeforeEachcleared the array, so test #2's
createdJobs[0]was the leaked test #1entry instead of its own — which is why test #2 reported
browserGpuMode: 'software'when it expected'auto'. Both failures hada single cause.
The reason this passed on PR #642's branch but failed once merged: PR #642
was the run that first introduced "forwards browserGpuMode='auto'" as
test #2 in this describe block. On its own branch, the previously-first
test ran without the per-iteration import latency stacking. After merge,
the test ordering plus full-build module graph put it past the 5 s edge
on every push.
How
beforeAllresolves the import once outside any test's timeout window,then the typed
renderLocal/resolveBrowserGpuForClireferences arereused by every
it(...)in the block. Cold-load happens once, everytest stays fast, no leaked promise can corrupt state. Behaviour identical
on Linux/macOS — local
bun run --cwd packages/cli testis still 22/22passing in <1 s.
Test plan
bun run --cwd packages/cli test→ 277/277 passing locally