can1357/oh-my-pi · error

Image download failed (${response.status}): ${rawText}

Error message

Image download failed (${response.status}): ${rawText}

What it means

The tool fetches an image from a remote URL and requires an HTTP 2xx; any non-OK response raises this error embedding the status code and the raw response body, which usually explains why the server refused (404 not found, 403 denied, 429 rate-limited, etc.). It surfaces the server's own error text so the failure cause is visible in the tool result.

Source

Thrown at packages/coding-agent/src/tools/image-gen.ts:397

	imageUrl: string,
	fetchImpl: FetchImpl,
	signal?: AbortSignal,
): Promise<InlineImageData> {
	if (imageUrl.startsWith("data:")) {
		const normalized = normalizeDataUrl(imageUrl.trim());
		if (!normalized.mimeType) {
			throw new Error("mime_type is required when providing raw base64 data.");
		}
		if (!normalized.data) {
			throw new Error("Image data is empty.");
		}
		return { data: normalized.data, mimeType: normalized.mimeType };
	}

	const response = await fetchImpl(imageUrl, { signal });
	if (!response.ok) {
		const rawText = await response.text();
		throw new Error(`Image download failed (${response.status}): ${rawText}`);
	}
	const contentType = response.headers.get("content-type")?.split(";")[0];
	if (!contentType?.startsWith("image/")) {
		throw new Error(`Unsupported image type from URL: ${imageUrl}`);
	}
	const buffer = await response.bytes();
	return { data: buffer.toBase64(), mimeType: contentType };
}

function collectOpenRouterResponseText(message: OpenRouterMessage | undefined): string | undefined {
	if (!message) return undefined;
	if (typeof message.content === "string") {
		const trimmed = message.content.trim();
		return trimmed.length > 0 ? trimmed : undefined;
	}
	if (Array.isArray(message.content)) {
		const texts = message.content
			.filter(part => part.type === "text")

View on GitHub (pinned to 9690622007)

Solutions

  1. Open the URL (or curl it) and read the embedded status/body message in the error to see the server's reason
  2. For 403/404 on signed URLs, regenerate a fresh signed URL before invoking the tool
  3. For 429, wait and retry with backoff, or host the image somewhere less rate-limited
  4. Verify the URL is reachable from the machine running the tool (proxy/VPN/firewall may block it)
Defensive patterns

Strategy: retry

Validate before calling

// optional pre-flight (only for critical flows)
const head = await fetch(imageUrl, { method: "HEAD" });
if (!head.ok) throw new Error(`Pre-flight failed: ${head.status} for ${imageUrl}`);

Try / catch

try {
	await genImage({ imageUrl });
} catch (err) {
	const m = /Image download failed \((\d+)\)/.exec(String(err));
	if (m && (m[1] === "429" || m[1].startsWith("5"))) {
		await Bun.sleep(2000); // retry with backoff
		return genImage({ imageUrl });
	}
	throw err; // 4xx: fix URL/auth, don't blind-retry
}

Prevention

When it happens

Trigger: loadImageFromUrl calls fetchImpl(imageUrl) and response.ok is false — any 4xx/5xx from the remote image host, e.g. expired signed URL, wrong path, CDN auth, or rate limiting during image-gen input-image collection.

Common situations: Pre-signed S3/GCS URLs that expired; hotlink protection or missing auth headers on a CDN; typo'd image URLs; 429s from provider throttling; image host behind a proxy returning HTML error pages.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/fa2c9438fb0685b7. Report an issue: GitHub.