jackwener/OpenCLI · error · ArgumentError
Image too large: ${(contentLength / 1024 / 1024).toFixed(1)}
Error message
Image too large: ${(contentLength / 1024 / 1024).toFixed(1)} MB (max ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024} MB) What it means
downloadRemoteImage reads the Content-Length header and throws ArgumentError if it exceeds MAX_IMAGE_SIZE_BYTES (20MB). This pre-flight check avoids buffering an oversized body before the same cap is re-checked against the actual downloaded byte length. Note: a missing Content-Length header is treated as 0, so this only fires when the server reports the size.
Source
Thrown at clis/twitter/utils.js:101
* @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 };
}
/**
* Attach a single image to the current /compose/post composer. Tries the
* native CDP file-input bridge first; falls back to a base64 DataTransfer
* shim if the bridge is missing or rejects with "Unknown action" /
* "not supported". Throws on hard failures.View on GitHub (pinned to 49907e53dc)
Solutions
- Choose a smaller variant of the image (CDN resize params like ?w=1600 or a thumbnail URL).
- Download, compress locally (`magick in.jpg -quality 85 out.jpg`), then use the local-path API.
- For GIFs, use a shorter/resized version (gifsicle) or convert to video.
- If the server lies about content-length while the real body is small, that cap lives in clis/twitter/utils.js (MAX_IMAGE_SIZE_BYTES) — but X's own upload limits still apply.
Example fix
// before
await downloadRemoteImage('https://cdn.example.com/originals/cat.png'); // 48 MB
// after
await downloadRemoteImage('https://cdn.example.com/originals/cat.png?w=1600&q=80'); // ~2 MB Defensive patterns
Strategy: validation
Validate before calling
const head = await fetch(imageUrl, { method: 'HEAD' });
const len = Number(head.headers.get('content-length') || '0');
if (len > 20 * 1024 * 1024) {
throw new Error(`Remote image too large: ${(len / 1048576).toFixed(1)} MB; use a smaller variant`);
} Try / catch
try {
await downloadRemoteImage(imageUrl);
} catch (err) {
if (err instanceof ArgumentError && /Image too large/.test(err.message)) {
const local = await downloadAndShrink(imageUrl, 1600);
return postWithImage(local);
}
throw err;
} Prevention
- Prefer CDN resize parameters (?w=1600&q=80) or thumbnail variants of large originals.
- HEAD-check content-length before downloading big links.
- Remember X's own limits (~5MB images, 15MB GIFs); keep media well under both caps.
When it happens
Trigger: Fetching a remote image whose Content-Length header exceeds 20*1024*1024 bytes: huge PNGs, long GIFs, or servers misreporting content-length larger than the actual body.
Common situations: Direct links to full-resolution photos (DSLR JPEGs >20MB); long animated GIFs hosted on CDNs; archives/media renamed to image extensions but served with correct large sizes.
Related errors
- Image too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB
- Detached HEAD — checkout a branch first
- 12306 queryByTrainNo returned HTTP ${resp.status}
- 12306 queryByTrainNo returned non-JSON body
- 12306 ${endpoint} returned non-JSON body
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/0ec559fd6d792637.
Report an issue: GitHub.