heygen-com/hyperframes · error · Error

freeze failed: HTTP ${res.status}

Error message

freeze failed: HTTP ${res.status}

What it means

Thrown by freezeUrl after the fetch when res.ok is false. The URL passed the host allowlist and the request was issued, but the response status was not 2xx — most often because the figma CDN URL is short-lived and expired, or because figma's S3 returned a 403/404 for a stale signed URL. freezeUrl surfaces the raw status so the caller can decide whether to re-render (get a fresh URL) or give up.

Source

Thrown at packages/core/src/figma/freeze.ts:54

 * crafted manifest/config URL (metadata endpoints, internal services).
 */
export function isAllowedFreezeUrl(url: string): boolean {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    return false;
  }
  if (parsed.protocol !== "https:") return false;
  const host = parsed.hostname;
  return host === "figma.com" || host.endsWith(".figma.com") || host.endsWith(".amazonaws.com");
}

export async function freezeUrl(url: string, destPath: string): Promise<number> {
  if (!isAllowedFreezeUrl(url))
    throw new Error(`freeze failed: refusing non-figma url ${url} (https + figma hosts only)`);
  const res = await fetch(url);
  if (!res.ok) throw new Error(`freeze failed: HTTP ${res.status}`);
  const declared = Number(res.headers.get("content-length") ?? 0);
  if (exceedsFreezeCap(declared))
    throw new Error(`freeze failed: content-length ${declared} exceeds ${MAX_FREEZE_BYTES} cap`);
  return freezeBytes(new Uint8Array(await res.arrayBuffer()), destPath);
}

export function freezeLocalFile(srcPath: string, destPath: string): void {
  const size = statSync(srcPath).size;
  if (exceedsFreezeCap(size))
    throw new Error(`freeze failed: ${size} bytes exceeds ${MAX_FREEZE_BYTES} cap`);
  mkdirSync(dirname(destPath), { recursive: true });
  copyFileSync(srcPath, destPath);
}

View on GitHub (pinned to c2996c8626)

Solutions

  1. Re-call renderNodes/renderNode to mint a fresh short-lived URL, then freeze it immediately.
  2. Never persist render URLs across sessions — always re-render and freeze in the same run.
  3. If the status is 5xx, retry after a short wait (CDN transient).
  4. Confirm no proxy is stripping the URL's signed query parameters.

Example fix

// before — render URL stored and used later, TTL expired
const { url } = await client.renderNode(ref, { format: 'png' });
// ...time passes...
await freezeUrl(url, dest); // HTTP 403

// after — render and freeze in the same tick
const { url } = await client.renderNode(ref, { format: 'png' });
await freezeUrl(url, dest);
Defensive patterns

Strategy: retry

Try / catch

async function freezeWithRefresh(url: string, dest: string, refresh: () => Promise<string>): Promise<number> {
  try {
    return await freezeUrl(url, dest);
  } catch (err) {
    if (err instanceof Error && /freeze failed: HTTP/.test(err.message)) {
      const fresh = await refresh(); // re-call renderNodes for a new signed URL
      return await freezeUrl(fresh, dest);
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: A figma render URL used after its short TTL expired (figma CDN URLs are signed and time-limited); a 403 from S3 for a revoked/aged signed URL; a 404 because the asset was garbage-collected; a network proxy returning a 502 for the figma CDN.

Common situations: Calling renderNodes, holding the returned URL for minutes/hours, then calling freezeUrl on it; saving a render URL to a manifest and reusing it in a later session; a flaky corporate proxy between the CLI and figma's CDN.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/dfd2a1fa48e8a525. Report an issue: GitHub.