{"record":{"id":"aade7d909a59d3c5","repo":"NousResearch/hermes-agent","slug":"could-not-fetch-image-response-status","errorCode":null,"errorMessage":"Could not fetch image: ${response.status}","messagePattern":"Could not fetch image: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/hooks/use-image-download.ts","lineNumber":28,"sourceCode":"\n  try {\n    return new URL(src, window.location.href).pathname.split('/').filter(Boolean).pop() || 'image'\n  } catch {\n    return src.split(/[\\\\/]/).filter(Boolean).pop() || 'image'\n  }\n}\n\nfunction isMissingIpcHandler(error: unknown): boolean {\n  const message = error instanceof Error ? error.message : typeof error === 'string' ? error : ''\n\n  return message.includes(\"No handler registered for 'hermes:saveImageFromUrl'\")\n}\n\nasync function startBrowserDownload(src: string) {\n  const response = await fetch(src)\n\n  if (!response.ok) {\n    throw new Error(`Could not fetch image: ${response.status}`)\n  }\n\n  const blobUrl = URL.createObjectURL(await response.blob())\n  const link = document.createElement('a')\n  link.href = blobUrl\n  link.download = imageFilename(src)\n  link.rel = 'noopener noreferrer'\n  document.body.appendChild(link)\n  link.click()\n  link.remove()\n  window.setTimeout(() => URL.revokeObjectURL(blobUrl), 30_000)\n}\n\n/** Save an image to disk via the desktop IPC bridge, falling back to a browser\n *  download when the handler is unavailable (older shell / web preview). */\nexport function useImageDownload(src?: string) {\n  const { t } = useI18n()\n  const copy = t.desktop","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/src/hooks/use-image-download.ts#L10-L46","documentation":"Thrown by startBrowserDownload() in apps/desktop/src/hooks/use-image-download.ts:28 — the fallback path of useImageDownload — when `fetch(src)` resolves with a non-OK HTTP status. The desktop first tries the IPC handler `hermes:saveImageFromUrl`; when that's missing (older shell / web preview) it fetches the image URL in the renderer and triggers a browser-style download, so any 4xx/5xx from the image host surfaces as this error.","triggerScenarios":"Image src returning 404 (expired signed URL, deleted file, typo); 401/403 for authenticated/CDN URLs requiring headers the renderer fetch cannot attach; 500 from the origin server; CORS-configured host returning an error status (fetch succeeds but response.ok false).","commonSituations":"An image-generation result URL that expired before the user clicked save; a gateway-hosted image on a remote backend whose auth token isn't propagated to the raw fetch; offline/proxy failures; the message referencing a local file path that the HTTP host doesn't serve.","solutions":["Verify the image URL still loads (open it directly, re-check for expired signatures) and retry the download after re-fetching a fresh URL.","For auth-protected images, prefer the desktop IPC path (update the desktop shell so saveImageFromUrl exists and fetches via the main process with proper auth).","If the source is a data: URL or local path, ensure it is converted to a fetchable/exportable form before download.","Handle the error with the hook's notifyError path and offer re-generation of the image."],"exampleFix":"// before\nconst response = await fetch(src)\nif (!response.ok) throw new Error(`Could not fetch image: ${response.status}`)\n\n// after — refresh a stale signed URL once, then retry\nlet url = src\nlet response = await fetch(url)\nif (response.status === 403 || response.status === 404) {\n  url = await refreshImageUrl(src)\n  response = await fetch(url)\n}\nif (!response.ok) throw new Error(`Could not fetch image: ${response.status}`)","handlingStrategy":"retry","validationCode":"async function isFetchable(src: string): Promise<boolean> {\n  try { const r = await fetch(src, { method: 'HEAD' }); return r.ok } catch { return false }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await startBrowserDownload(src)\n} catch (e) {\n  if (e instanceof Error && /^Could not fetch image: (401|403|404)$/.test(e.message)) {\n    const fresh = await refreshImageUrl(src) // re-sign / re-host\n    if (fresh) await startBrowserDownload(fresh)\n    else notifyError(e)\n  } else throw e\n}","preventionTips":["Save images promptly before signed URLs expire","Prefer the desktop IPC saveImageFromUrl path (auth handled in main process) over renderer fetch","HEAD-check questionable URLs before showing a download button","Don't leave download handlers with no error UI — surface status codes to the user"],"tags":["desktop","images","network","http","download"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}