heygen-com/hyperframes · error
Failed to download ${url}: HTTP ${res.status} ${res.statusTe
Error message
Failed to download ${url}: HTTP ${res.status} ${res.statusText} What it means
Thrown by downloadToFile when the fetch resolves with res.ok === false. The message surfaces the URL, HTTP status, and statusText verbatim so the caller can tell an expired presigned URL (403), a missing object (404), or a server-side failure (5xx) apart. It fires before any file is opened, so no partial artifact is created at destPath.
Source
Thrown at packages/cli/src/cloud/download.ts:47
bytes: number;
}
/**
* Stream `url` into `destPath`. Creates the parent directory if needed,
* truncates any existing file at the destination, and deletes the
* partial output on any error so the caller never observes a corrupt
* file at the returned path.
*/
// fallow-ignore-next-line complexity
export async function downloadToFile(
url: string,
destPath: string,
options: DownloadOptions = {},
): Promise<DownloadResult> {
const fetchImpl = options.fetchImpl ?? fetch;
const res = await fetchImpl(url, { signal: options.signal });
if (!res.ok) {
throw new Error(`Failed to download ${url}: HTTP ${res.status} ${res.statusText}`);
}
if (!res.body) {
throw new Error(`Failed to download ${url}: empty response body`);
}
mkdirSync(dirname(destPath), { recursive: true });
const totalHeader = res.headers.get("content-length");
const total = totalHeader ? Number.parseInt(totalHeader, 10) : undefined;
const totalOpt = total !== undefined && Number.isFinite(total) ? total : undefined;
const file = createWriteStream(destPath);
let bytes = 0;
let errored = false;
try {
for await (const chunk of res.body as unknown as AsyncIterable<Uint8Array>) {
if (options.signal?.aborted) {
throw options.signal.reason instanceof ErrorView on GitHub (pinned to c2996c8626)
Solutions
- Refetch a fresh asset/URL via `hyperframes cloud get` (or the equivalent reserve call) and retry — most cases are expired presigned URLs.
- Confirm the object still exists in the cloud (status 404 means it is gone; you must re-create or re-upload it).
- For 5xx, retry with backoff a couple of times before treating as a hard failure.
- If 401/403 persists on a freshly issued URL, check credentials and clock skew on the signing host.
Example fix
// before: a possibly-stale URL reused across retries
await downloadToFile(staleUrl, dest);
// after: refresh the presigned URL when the download 403s
try {
await downloadToFile(url, dest);
} catch (err) {
if (/HTTP 403/.test(String(err?.message))) {
const fresh = await client.getAssetUrl(id);
await downloadToFile(fresh, dest);
} else throw err;
} Defensive patterns
Strategy: retry
Validate before calling
async function assertDownloadable(url: string): Promise<void> {
const res = await fetch(url, { method: 'GET' });
if (!res.ok) throw new Error(`preflight: HTTP ${res.status}`);
res.body?.cancel();
} Try / catch
async function downloadWithRefresh(getUrl: () => Promise<string>, dest: string) {
for (let attempt = 0; attempt < 3; attempt++) {
try {
await downloadToFile(await getUrl(), dest);
return;
} catch (err) {
if (/HTTP 40[03]/.test(String(err?.message)) && attempt < 2) continue;
throw err;
}
}
} Prevention
- Start the download immediately after reserving a presigned URL to stay inside its TTL.
- Re-reserve the URL on 403/404 instead of retrying the stale one.
- For 5xx, retry with backoff a small, bounded number of times.
When it happens
Trigger: Downloading a presigned S3/GCS URL after its expiry window (403 Forbidden), fetching an asset whose object was deleted between manifest generation and download (404), a 5xx from the object store, or any redirect/auth failure that returns non-2xx.
Common situations: A presigned URL sits idle past its TTL (common when the asset was reserved, then the download was queued behind other work); the cloud asset was garbage-collected; a network appliance rewrites the response to a 401 captive-portal page.
Related errors
- Truncated download: got ${bytes} bytes, expected ${totalOpt}
- Failed to download ${url}: empty response body
- Model download failed: ${model}
- Invalid JSON response: ${(err as Error).message}
- Direct upload PUT failed: ${res.status} ${res.statusText}${d
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/a51e71ba70717719.
Report an issue: GitHub.