decolua/9router · error

Failed to fetch image: ${res.status}

Error message

Failed to fetch image: ${res.status}

What it means

Thrown by urlToBase64 (open-sse/handlers/imageProviders/_base.js:24) when fetching an image URL returned by a generation provider fails with a non-OK HTTP status. Some image providers (e.g. those returning hosted URLs instead of base64) require a second fetch to download the image so it can be returned as b64_json; this error surfaces the upstream status of that download, not of the generation call itself.

Source

Thrown at open-sse/handlers/imageProviders/_base.js:24

export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// Map OpenAI size to provider-specific aspect ratio
export function sizeToAspectRatio(size) {
  if (!size || typeof size !== "string") return "1:1";
  const map = {
    "1024x1024": "1:1",
    "1024x1792": "9:16",
    "1792x1024": "16:9",
    "1024x1536": "2:3",
    "1536x1024": "3:2",
  };
  return map[size] || "1:1";
}

// Fetch URL → base64 (for providers returning image URLs)
export async function urlToBase64(url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Failed to fetch image: ${res.status}`);
  const buf = await res.arrayBuffer();
  return Buffer.from(buf).toString("base64");
}

export function nowSec() {
  return Math.floor(Date.now() / 1000);
}

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Retry the generation (or just the URL fetch after a short delay) — transient 5xx/429 on the CDN usually clears.
  2. Fetch and convert the URL to base64 immediately after generation before any signed URL expires.
  3. Check the status code in the message: 403 usually means the URL requires auth/headers — consider proxying the fetch with appropriate headers; 404 means the asset is gone and the image must be regenerated.
  4. If a provider consistently returns undownloadable URLs, prefer a provider/response mode that returns b64 directly.

Example fix

// before
const res = await fetch(url);
// after: retry with backoff before giving up
let res;
for (let i = 0; i < 3; i++) {
  res = await fetch(url);
  if (res.ok) break;
  await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
if (!res.ok) throw new Error(`Failed to fetch image: ${res.status}`);
Defensive patterns

Strategy: retry

Validate before calling

async function isUrlFetchable(url) {
  try { const r = await fetch(url, { method: "HEAD" }); return r.ok; } catch { return false; }
}

Type guard

function isHttpUrl(v) {
  try { const u = new URL(v); return u.protocol === "https:" || u.protocol === "http:"; } catch { return false; }
}

Try / catch

try {
  const b64 = await urlToBase64(imageUrl);
} catch (e) {
  if (/Failed to fetch image: (429|5\d\d)/.test(e.message)) {
    await delay(2000);
    return retryUrlToBase64(imageUrl, 3);
  }
  if (/Failed to fetch image: (403|404)/.test(e.message)) {
    return regenerateImage(prompt); // signed URL expired or purged
  }
  throw e;
}

Prevention

When it happens

Trigger: A provider's image result contains an image URL; urlToBase64 is called (from handleImageGenerationCore or b64 helpers) and fetch(url) resolves with res.ok === false — 403 (URL signed/expired or hotlink-protected), 404 (host purged the file), 429 (rate limited), or 5xx from the image CDN.

Common situations: Provider URLs expire within minutes (common with CDN-signed links) and the app fetched too late; the image host blocks requests without a Referer/User-Agent; transient CDN errors during a burst of generations.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/33dbb707b2438a30. Report an issue: GitHub.