heygen-com/hyperframes · error · PollTimeoutError
Render ${lastDetail.render_id} did not reach a terminal stat
Error message
Render ${lastDetail.render_id} did not reach a terminal state within ${Math.round(elapsedMs / 1000)}s What it means
PollTimeoutError is thrown by pollUntilTerminal when the render has not reached 'completed' or 'failed' within maxWaitMs (default 60 min, interval 10s). It is a dedicated Error subclass that carries lastDetail (the most recent GET /v3/hyperframes/renders/{id} response) so callers can inspect status, progress, and any partial error info. Underlying request errors (404/401/5xx) bubble immediately and are intentionally NOT retried inside the loop.
Source
Thrown at packages/cli/src/cloud/poll.ts:83
// instead of waiting out the full interval. Tests inject a no-op
// sleep that ignores the signal — that's fine, they don't abort.
const sleep = options.sleep ?? defaultAbortableSleep(options.signal);
const started = now();
while (true) {
if (options.signal?.aborted) {
throw signalAbortError(options.signal);
}
const detail = await client.getRender({ render_id: renderId, signal: options.signal });
const elapsed = now() - started;
options.onTick?.(detail, elapsed);
if (isTerminal(detail.status)) {
return detail;
}
if (elapsed >= maxWaitMs) {
throw new PollTimeoutError(detail, elapsed);
}
await sleep(intervalMs);
}
}
function signalAbortError(signal: AbortSignal): Error {
const reason = signal.reason;
return reason instanceof Error ? reason : new Error("Poll aborted");
}
function defaultAbortableSleep(signal?: AbortSignal): (ms: number) => Promise<void> {
// fallow-ignore-next-line complexity
return (ms: number) =>
new Promise<void>((resolve, reject) => {
const onAbort = (): void => {
clearTimeout(timer);
reject(signalAbortError(signal!));
};View on GitHub (pinned to c2996c8626)
Solutions
- Catch PollTimeoutError specifically, read err.lastDetail, and decide: resume polling with another pollUntilTerminal call, or surface failure.
- If the render is legitimately long, raise maxWaitMs (e.g. 2h) and pass a generous intervalMs.
- Check the render id via `hyperframes cloud status <id>` or the dashboard to see if the workflow is actually progressing.
- If lastDetail.status is stale across many ticks, report a stuck workflow to the API owner rather than looping forever.
Example fix
// before: single 60-min cap, hard fail on timeout
const detail = await pollUntilTerminal(client, id);
// after: resume across multiple windows, keep lastDetail
let detail;
try {
detail = await pollUntilTerminal(client, id, { maxWaitMs: 30 * 60_000 });
} catch (err) {
if (err instanceof PollTimeoutError) {
detail = err.lastDetail; // inspect, then resume or report
detail = await pollUntilTerminal(client, id, { maxWaitMs: 30 * 60_000 });
} else throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check: confirm the render id exists before polling
const detail = await client.getRender({ render_id: id });
if (!detail) throw new Error(`render ${id} not found`); Type guard
function isPollTimeoutError(err: unknown): err is PollTimeoutError {
return err instanceof Error && err.name === 'PollTimeoutError';
} Try / catch
try {
detail = await pollUntilTerminal(client, id, { maxWaitMs: 30 * 60_000 });
} catch (err) {
if (err instanceof PollTimeoutError) {
detail = err.lastDetail; // inspect, then resume or escalate
} else throw err;
} Prevention
- Size maxWaitMs to the worst-case render duration, not the average.
- Use err.lastDetail to decide resume vs. escalate rather than looping blindly.
- Surface stuck workflows (status unchanged across ticks) to the API owner.
When it happens
Trigger: A render that stays in 'queued'/'processing' longer than maxWaitMs; a Temporal workflow whose start_to_close is longer than the poll cap; the API stalling without flipping to a terminal status; maxWaitMs explicitly lowered by the caller for a fast-fail policy.
Common situations: A genuinely long render (high-resolution, many scenes) exceeding the default cap; a stuck server-side workflow; calling poll with a too-small maxWaitMs; network blips that repeatedly delay each getRender without erroring.
Related errors
- BeginFrame probe timeout before ${label}
- [getRenderProgress] executionArn is required
- [renderToLambda] bucketName is required
- [renderToLambda] stateMachineArn is required
- [renderToLambda] either siteHandle or projectDir must be sup
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/ec2f5a910b5be1db.
Report an issue: GitHub.