moeru-ai/airi · error · Error

Failed to fetch image: ${response.statusText}

Error message

Failed to fetch image: ${response.statusText}

What it means

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.

Source

Thrown at apps/stage-tamagotchi/src/main/services/airi/widgets/artistry-bridge.ts:85

 */
const cardDefaults: ArtistrySyncSnapshot = {
  provider: undefined as string | undefined,
  model: undefined as string | undefined,
  promptPrefix: undefined as string | undefined,
  options: undefined as Record<string, unknown> | undefined,
  globals: undefined as Record<string, unknown> | undefined,
}

function createRunId(widgetId: string) {
  return `${widgetId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`
}

async function downloadImageAsBase64(url: string): Promise<string> {
  try {
    log.log(`[Artistry Bridge] Downloading image from: ${url}`)
    const response = await fetch(url)
    if (!response.ok)
      throw new Error(`Failed to fetch image: ${response.statusText}`)
    const buffer = await response.arrayBuffer()
    const base64 = Buffer.from(buffer).toString('base64')
    // NOTICE: Downstream renderer paths consume this via fetch(), which requires a data URL.
    return `data:image/png;base64,${base64}`
  }
  catch (error: unknown) {
    log.error(`[Artistry Bridge] Failed to download image: ${errorMessageFrom(error)}`)
    throw error
  }
}

function supportsJobCallback(provider: ArtistryProvider): provider is ArtistryProvider & Required<Pick<ArtistryProvider, 'setJobCallback'>> {
  return typeof provider.setJobCallback === 'function'
}

// Maintaining a registry of providers
export const artistryProviders = new Map<string, ArtistryProvider>()
artistryProviders.set('comfyui', new ComfyUIProvider())

View on GitHub (pinned to 27111382b4)

Solutions

  1. 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.).
  2. If the URL is pre-signed/expiring, download the image sooner after generation or configure the provider for a longer link TTL.
  3. Ensure the Electron main process network environment (proxy, DNS, certs) can reach the image host.
  4. Add a retry with backoff for transient 5xx responses.

Example fix

// before
const response = await fetch(url)
if (!response.ok)
  throw new Error(`Failed to fetch image: ${response.statusText}`)

// after
const response = await fetchWithRetry(url, { retries: 3 })
if (!response.ok)
  throw new Error(`Failed to fetch image after retries: ${response.status} ${response.statusText}`)
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: only attempt download for reachable, ok URLs
async function isImageReachable(url: string): Promise<boolean> {
  try {
    const head = await fetch(url, { method: 'HEAD' })
    return head.ok
  } catch {
    return false
  }
}

Try / catch

async function downloadWithRetry(url: string, attempts = 3): Promise<string> {
  let lastErr: unknown
  for (let i = 0; i < attempts; i++) {
    try {
      return await downloadImageAsBase64(url)
    } catch (e) {
      lastErr = e
      await new Promise(r => setTimeout(r, 1000 * (i + 1)))
    }
  }
  throw lastErr
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/82c40297e325b311. Report an issue: GitHub.