{"record":{"id":"6c827ad75a5b85c2","repo":"Stirling-Tools/Stirling-PDF","slug":"download-failed-response-status","errorCode":null,"errorMessage":"Download failed (${response.status})","messagePattern":"Download failed \\((.+?)\\)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/desktop/services/downloadService.ts","lineNumber":43,"sourceCode":"    return { cancelled: true };\n  }\n\n  const result = await saveToLocalPath(request.data, savePath);\n  if (!result.success) {\n    throw new Error(result.error || \"Failed to save file\");\n  }\n\n  return { savedPath: savePath };\n}\n\nexport async function downloadFromUrl(\n  url: string,\n  filename: string,\n  localPath?: string,\n): Promise<DownloadResult> {\n  const response = await fetch(url);\n  if (!response.ok) {\n    throw new Error(`Download failed (${response.status})`);\n  }\n  const blob = await response.blob();\n  return downloadFile({ data: blob, filename, localPath });\n}\n","sourceCodeStart":25,"sourceCodeEnd":48,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/desktop/services/downloadService.ts#L25-L48","documentation":"Thrown by downloadFromUrl() when fetch(url) resolves with response.ok === false. The HTTP status code is interpolated into the message. The blob is never read; this guards upstream errors (auth required, not found, server error) before attempting to save.","triggerScenarios":"Fetching the source URL returns 4xx/5xx: 401/403 when the resource needs auth the desktop didn't send, 404 for an expired/missing link, 500 from a failing backend, or a CDN/signed-URL that has expired.","commonSituations":"Expired presigned/S3 URL; backend endpoint that requires a JWT the caller didn't attach; tool output that was cleaned up server-side; wrong URL passed by the caller.","solutions":["Parse the status from the message (or refactor to expose it) and handle 401/403 by refreshing the token, 404 by re-fetching a fresh link, 5xx by retrying.","Ensure the request carries the needed Authorization header / cookies for protected resources.","For signed URLs, generate a fresh one before retrying.","Log the URL (without secrets) + status to identify the failing resource."],"exampleFix":"// before\nawait downloadFromUrl(url, name);\n\n// after: retry on transient 5xx, refresh auth on 401\ntry { await downloadFromUrl(url, name); }\ncatch (e) {\n  const status = Number(/\\((\\d+)\\)/.exec(e.message)?.[1]);\n  if (status === 401) { await authService.refresh(); await downloadFromUrl(url, name); return; }\n  if (status >= 500) { /* retry with backoff */ return; }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// sanity-check the URL before fetching\nfunction isDownloadableUrl(u: string): boolean { try { const p = new URL(u); return p.protocol === 'http:' || p.protocol === 'https:'; } catch { return false; } }","typeGuard":"function isDownloadStatusError(e: unknown): e is Error {\n  return e instanceof Error && /^Download failed \\(\\d+\\)$/.test(e.message);\n}","tryCatchPattern":"try { await downloadFromUrl(url, name); }\ncatch (e) {\n  if (isDownloadStatusError(e)) {\n    const status = Number(/\\((\\d+)\\)/.exec(e.message)?.[1]);\n    if (status === 401) { await authService.refresh(); return downloadFromUrl(url, name); }\n    if (status >= 500) { /* retry with backoff */ return; }\n  }\n  throw e;\n}","preventionTips":["Attach the auth token to protected download URLs.","Refresh signed URLs before they expire.","Parse the status to drive retry vs re-auth vs give-up."],"tags":["network","download","http-status","desktop"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}