{"record":{"id":"82c40297e325b311","repo":"moeru-ai/airi","slug":"failed-to-fetch-image-response-statustext","errorCode":null,"errorMessage":"Failed to fetch image: ${response.statusText}","messagePattern":"Failed to fetch image: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/widgets/artistry-bridge.ts","lineNumber":85,"sourceCode":" */\nconst cardDefaults: ArtistrySyncSnapshot = {\n  provider: undefined as string | undefined,\n  model: undefined as string | undefined,\n  promptPrefix: undefined as string | undefined,\n  options: undefined as Record<string, unknown> | undefined,\n  globals: undefined as Record<string, unknown> | undefined,\n}\n\nfunction createRunId(widgetId: string) {\n  return `${widgetId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`\n}\n\nasync function downloadImageAsBase64(url: string): Promise<string> {\n  try {\n    log.log(`[Artistry Bridge] Downloading image from: ${url}`)\n    const response = await fetch(url)\n    if (!response.ok)\n      throw new Error(`Failed to fetch image: ${response.statusText}`)\n    const buffer = await response.arrayBuffer()\n    const base64 = Buffer.from(buffer).toString('base64')\n    // NOTICE: Downstream renderer paths consume this via fetch(), which requires a data URL.\n    return `data:image/png;base64,${base64}`\n  }\n  catch (error: unknown) {\n    log.error(`[Artistry Bridge] Failed to download image: ${errorMessageFrom(error)}`)\n    throw error\n  }\n}\n\nfunction supportsJobCallback(provider: ArtistryProvider): provider is ArtistryProvider & Required<Pick<ArtistryProvider, 'setJobCallback'>> {\n  return typeof provider.setJobCallback === 'function'\n}\n\n// Maintaining a registry of providers\nexport const artistryProviders = new Map<string, ArtistryProvider>()\nartistryProviders.set('comfyui', new ComfyUIProvider())","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/main/services/airi/widgets/artistry-bridge.ts#L67-L103","documentation":"Thrown by downloadImageAsBase64 when fetch(url) returns a non-ok HTTP response while downloading a generated image for the artistry bridge. The error carries the response's statusText so the caller can see what the image host returned.","triggerScenarios":"After a provider reports a succeeded job with an imageUrl, downloadImageAsBase64 fetches that URL; if the host returns 4xx/5xx (expired link, 404, auth failure, server error) the error is thrown.","commonSituations":"The image URL is a temporary/pre-signed link that expired between generation and download; the provider host is behind auth not available to the Electron main process; a CDN returns 403/404; network proxy or DNS issues cause a 502/503; the URL is localhost-served but the port is wrong.","solutions":["Check response.status/statusText in the logs to identify the HTTP failure code, then address that code (refresh token for 401, fix URL for 404, etc.).","If the URL is pre-signed/expiring, download the image sooner after generation or configure the provider for a longer link TTL.","Ensure the Electron main process network environment (proxy, DNS, certs) can reach the image host.","Add a retry with backoff for transient 5xx responses."],"exampleFix":"// before\nconst response = await fetch(url)\nif (!response.ok)\n  throw new Error(`Failed to fetch image: ${response.statusText}`)\n\n// after\nconst response = await fetchWithRetry(url, { retries: 3 })\nif (!response.ok)\n  throw new Error(`Failed to fetch image after retries: ${response.status} ${response.statusText}`)","handlingStrategy":"retry","validationCode":"// Pre-flight: only attempt download for reachable, ok URLs\nasync function isImageReachable(url: string): Promise<boolean> {\n  try {\n    const head = await fetch(url, { method: 'HEAD' })\n    return head.ok\n  } catch {\n    return false\n  }\n}","typeGuard":null,"tryCatchPattern":"async function downloadWithRetry(url: string, attempts = 3): Promise<string> {\n  let lastErr: unknown\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await downloadImageAsBase64(url)\n    } catch (e) {\n      lastErr = e\n      await new Promise(r => setTimeout(r, 1000 * (i + 1)))\n    }\n  }\n  throw lastErr\n}","preventionTips":["Download generated images promptly before pre-signed links expire.","Ensure the Electron main process has the same network access (proxy/DNS) as the renderer.","Log response.status alongside statusText for richer diagnostics."],"tags":["network","artistry","image-download","fetch"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}