heygen-com/hyperframes · error · StageBudgetTimeoutError
stage budget exceeded while ${label} (${budgetMs}ms)
Error message
stage budget exceeded while ${label} (${budgetMs}ms) What it means
Thrown by withRemainingBudget() when a capture stage exceeded its allotted wall-clock budget. The function races the work promise against a setTimeout(remainingMs); if the timer fires first (or remainingMs was already <= 0 on entry), it rejects with a StageBudgetTimeoutError carrying the label and budgetMs. This is the mechanism that bounds each capture stage so a hung page does not stall the whole render indefinitely.
Source
Thrown at packages/cli/src/capture/captureTimeout.ts:63
export function isProtocolEvaluateTimeoutError(err: unknown): boolean {
if (err instanceof TimeoutError) {
return !hasNavigationTimeoutMessage(err.message);
}
return hasProtocolEvaluateTimeoutMessage(errorMessage(err));
}
export function isDegradableEvaluateTimeoutError(err: unknown): boolean {
return isStageBudgetTimeoutError(err) || isProtocolEvaluateTimeoutError(err);
}
export async function withRemainingBudget<T>(
work: Promise<T>,
remainingMs: number,
label: string,
): Promise<T> {
if (!(remainingMs > 0)) {
throw new StageBudgetTimeoutError(label, Math.max(0, remainingMs));
}
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
work,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => {
reject(new StageBudgetTimeoutError(label, remainingMs));
}, remainingMs);
}),
]);
} finally {
if (timer !== undefined) {
clearTimeout(timer);
}
}
}View on GitHub (pinned to c2996c8626)
Solutions
- Increase the stage/capture timeout if the composition legitimately needs more time (pass a larger --timeout or the relevant option).
- Profile the composition's in-page script — a hang usually means an infinite loop or a synchronous wait; optimize or make it async.
- Ensure external resources the page depends on are local or fast (deterministic rendering forbids render-time network fetches).
- If the budget was already <= 0 on entry, an earlier stage overran — investigate the label of the prior stage.
Example fix
// before — default timeout too tight for heavy comp
$ hyperframes render heavy.html
// allow more time
$ hyperframes render heavy.html --timeout 120000
// in composition: avoid blocking the main thread
// before (hangs): while(true){}
// after: yield with requestAnimationFrame Defensive patterns
Strategy: retry
Validate before calling
// Ensure the stage budget is positive before calling withRemainingBudget.
if (!(remainingMs > 0)) {
throw new Error(`No budget left for stage '${label}' — increase the capture timeout.`);
} Try / catch
import { isDegradableEvaluateTimeoutError } from '@hyperframes/cli/capture/captureTimeout';
try {
await withRemainingBudget(work, remainingMs, label);
} catch (err) {
if (isDegradableEvaluateTimeoutError(err)) {
// degrade gracefully: skip optional capture step or lower fidelity
} else throw err;
} Prevention
- Size the capture timeout to the composition's complexity; raise --timeout for heavy comps.
- Keep in-page scripts non-blocking; yield to the event loop where possible.
- Avoid render-time network fetches in compositions (deterministic-rendering rule).
- Use isDegradableEvaluateTimeoutError to classify retryable vs fatal capture failures.
When it happens
Trigger: withRemainingBudget(work, remainingMs, label) is called with a remaining budget that is exhausted either up-front (remainingMs <= 0) or because the work promise did not settle before the setTimeout fired. Typical during in-page script evaluation that hangs (infinite loop, blocked on a resource).
Common situations: Composition runs a heavy in-page animation/JS that blocks the main thread past the stage budget; a network resource the page waits on is slow/blocked; the overall capture timeout is too tight for a complex composition; the page hung on a WebGPU/shader compile.
Related errors
- beginFrame probe timeout before ${label}
- BeginFrame probe timeout before ${label}
- Model did not return output '${outputName}'
- No frames produced from ${inputPath}. Decoder stderr: ${deco
- ${err instanceof Error ? err.message : String(err)}${detail}
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/16d2d7d2080c6b47.
Report an issue: GitHub.