jackwener/OpenCLI · error · ArgumentError
Image download failed: HTTP ${response.status}
Error message
Image download failed: HTTP ${response.status} What it means
After fetching the remote image, downloadRemoteImage checks response.ok; any non-2xx HTTP status (404 Not Found, 403 Forbidden, 410 Gone, 5xx) throws ArgumentError `Image download failed: HTTP <status>`. The URL was valid but the server refused or could not serve the image.
Source
Thrown at clis/twitter/utils.js:97
* Download a remote image to a per-call tmp directory. Returns the absolute
* path on success. Caller owns the tmp dir and must clean it up. Throws
* {@link ArgumentError} on bad input or download failure.
*
* @returns {Promise<{ absPath: string, cleanupDir: string }>}
*/
export async function downloadRemoteImage(imageUrl) {
let parsed;
try {
parsed = new URL(imageUrl);
} catch {
throw new ArgumentError(`Invalid image URL: ${imageUrl}`);
}
if (!/^https?:$/.test(parsed.protocol)) {
throw new ArgumentError(`Unsupported image URL protocol: ${parsed.protocol}`);
}
const response = await fetch(imageUrl);
if (!response.ok) {
throw new ArgumentError(`Image download failed: HTTP ${response.status}`);
}
const contentLength = Number(response.headers.get('content-length') || '0');
if (contentLength > MAX_IMAGE_SIZE_BYTES) {
throw new ArgumentError(`Image too large: ${(contentLength / 1024 / 1024).toFixed(1)} MB (max ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024} MB)`);
}
const ext = resolveImageExtension(imageUrl, response.headers.get('content-type'));
const cleanupDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-twitter-'));
const absPath = path.join(cleanupDir, `image${ext}`);
const buffer = Buffer.from(await response.arrayBuffer());
if (buffer.byteLength > MAX_IMAGE_SIZE_BYTES) {
fs.rmSync(cleanupDir, { recursive: true, force: true });
throw new ArgumentError(`Image too large: ${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB (max ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024} MB)`);
}
fs.writeFileSync(absPath, buffer);
return { absPath, cleanupDir };
}
/**View on GitHub (pinned to 49907e53dc)
Solutions
- Check the status code: 404 → fix the URL/confirm the image still exists; 403 → the host requires auth or blocks hotlinking; 5xx → retry later.
- Regenerate expired signed URLs (fresh S3/CDN signature) before downloading.
- Download the image manually (browser/curl with proper headers, e.g. a Referer or User-Agent) and pass the local file instead.
- Add a retry with backoff for transient 5xx responses.
Example fix
// before
await downloadRemoteImage('https://s3.amazonaws.com/bucket/cat.png?X-Amz-Expires=60&...'); // expired
// after
const freshUrl = await getFreshPresignedUrl('bucket', 'cat.png');
await downloadRemoteImage(freshUrl); Defensive patterns
Strategy: retry
Validate before calling
const head = await fetch(imageUrl, { method: 'HEAD' });
if (!head.ok) {
throw new Error(`Image URL not fetchable (HTTP ${head.status}); fix or refresh the URL`);
} Try / catch
try {
await downloadRemoteImage(imageUrl);
} catch (err) {
const m = err instanceof ArgumentError && err.message.match(/HTTP (\d+)/);
if (m && +m[1] >= 500) return withRetry(() => downloadRemoteImage(imageUrl), 3); // transient
if (m && (+m[1] === 403 || +m[1] === 404)) throw new Error('URL expired or auth-walled; regenerate it');
throw err;
} Prevention
- Regenerate presigned/CDN URLs shortly before download; they expire.
- Retry with backoff only on 429/5xx, not 4xx client errors.
- For auth-walled hosts, download manually with proper cookies/headers and pass a local file.
When it happens
Trigger: fetch() returns a 4xx/5xx for the image URL: deleted or renamed asset, expired signed/CDN URL (403), hotlink protection, auth-walled image host, or origin/server error (5xx).
Common situations: S3/CloudFront presigned URLs that expired; images behind logins (Instagram/private hosts); Instagram/Twitter CDN links that rotate; transient 502/503 from the image host.
Related errors
- 12306 queryByTrainNo returned HTTP ${resp.status}
- 12306 queryByTrainNo returned non-JSON body
- 12306 ${endpoint} returned non-JSON body
- 1point3acres request failed: HTTP ${res.status} ${res.status
- ${label} returned HTTP ${resp.status}: ${summarizeApiError(p
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/d6caec66f7e3ee4e.
Report an issue: GitHub.