{"record":{"id":"d0cc410c3544dcfe","repo":"windmill-labs/windmill","slug":"http-res-status-res-statustext","errorCode":null,"errorMessage":"HTTP ${res.status} ${res.statusText}","messagePattern":"HTTP \\$\\{res\\.status\\} \\$\\{res\\.statusText\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/recording/rawAppRecordingLoad.ts","lineNumber":613,"sourceCode":"\t\t\t// `type` is caller-controlled and only structurally bounded: a payload can\n\t\t\t// carry megabytes in it and reach the page as text. Name it only when it is\n\t\t\t// short enough to be a kind rather than a payload.\n\t\t\tconst named = typeof type === 'string' && type.length <= 32 ? ` (${type})` : ''\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `This recording is of an unknown kind${named} — it may need a newer Windmill.`\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Fetch a recording from `url`, enforcing the download cap while streaming. */\nexport async function fetchRecording(\n\turl: string,\n\tonProgress?: (loaded: number, total: number) => void\n): Promise<unknown> {\n\tconst res = await fetch(url)\n\tif (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)\n\tconst total = Number(res.headers.get('content-length')) || 0\n\tif (total > MAX_RECORDING_BYTES) throw new Error(`Recording is too large (${total} bytes)`)\n\tconst reader = res.body?.getReader()\n\tif (!reader) {\n\t\tconst text = await res.text()\n\t\tif (text.length > MAX_RECORDING_BYTES) throw new Error('Recording exceeded the size limit')\n\t\treturn JSON.parse(text)\n\t}\n\tconst chunks: Uint8Array[] = []\n\tlet loaded = 0\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read()\n\t\tif (done) break\n\t\tif (!value) continue\n\t\tchunks.push(value)\n\t\tloaded += value.length\n\t\tif (loaded > MAX_RECORDING_BYTES) {\n\t\t\tawait reader.cancel()","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/recording/rawAppRecordingLoad.ts#L595-L631","documentation":"Thrown by fetchRecording when the HTTP response for the recording URL is not ok — it surfaces the raw status and statusText from the server hosting the recording file. 404 means the file is gone, 401/403 an access problem, 5xx a server failure; the recording never starts downloading.","triggerScenarios":"fetchRecording(url) where the server returns 404 (file removed), 403 (no permission), 500, or any non-2xx status.","commonSituations":"A recording URL from an old share link whose underlying file was deleted; expired signed storage URL; wrong workspace/base URL in the link.","solutions":["Check the URL is correct and still points at an existing recording file","Re-capture or re-export the recording if the source run/file is gone","Verify permissions/authentication for the storage serving the file"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const head = await fetch(url, { method: 'HEAD' })\nif (!head.ok) throw new Error(`Recording unavailable: ${head.status}`)","typeGuard":"function isOk(res: Response): res is Response & { ok: true } { return res.ok }","tryCatchPattern":"try { rec = await fetchRecording(url) } catch (e) { if (/^HTTP \\d+/.test(String(e))) notify('Recording unavailable at ' + url); else throw e }","preventionTips":["Validate share URLs before storing them","Use stable, permissioned storage for recordings","Surface HTTP status to the user rather than a generic failure"],"tags":["http","fetch","network","recording"],"backgroundTag":"http-request-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}