jackwener/OpenCLI · error · ArgumentError

Image too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB

Error message

Image too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB (max ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024} MB)

What it means

resolveImagePath enforces MAX_IMAGE_SIZE_BYTES = 20MB (a safety net above Twitter's own ~5MB image / 15MB GIF limits). If fs.statSync reports a larger file it throws ArgumentError with the size in MB. Oversized media would fail later at X's upload endpoint, so it is rejected up front.

Source

Thrown at clis/twitter/utils.js:51

/**
 * Validate a single image path. Throws {@link ArgumentError} on bad input
 * (typed input failure surfaces before any browser interaction).
 *
 * @param {string} imagePath - Local filesystem path, may be relative.
 * @returns {string} Absolute resolved path.
 */
export function resolveImagePath(imagePath) {
    const absPath = path.resolve(imagePath);
    if (!fs.existsSync(absPath)) {
        throw new ArgumentError(`Image file not found: ${absPath}`);
    }
    const ext = path.extname(absPath).toLowerCase();
    if (!SUPPORTED_IMAGE_EXTENSIONS.has(ext)) {
        throw new ArgumentError(`Unsupported image format "${ext}". Supported: jpg, jpeg, png, gif, webp`);
    }
    const stat = fs.statSync(absPath);
    if (stat.size > MAX_IMAGE_SIZE_BYTES) {
        throw new ArgumentError(`Image too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB (max ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024} MB)`);
    }
    return absPath;
}

/**
 * Resolve the file extension to use when persisting a remote image: prefer
 * Content-Type, fall back to URL pathname.
 */
export function resolveImageExtension(url, contentType) {
    const normalizedContentType = (contentType || '').split(';')[0].trim().toLowerCase();
    if (normalizedContentType && CONTENT_TYPE_TO_EXTENSION[normalizedContentType]) {
        return CONTENT_TYPE_TO_EXTENSION[normalizedContentType];
    }
    try {
        const pathname = new URL(url).pathname;
        const ext = path.extname(pathname).toLowerCase();
        if (SUPPORTED_IMAGE_EXTENSIONS.has(ext))
            return ext;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Compress or resize the image before uploading, e.g. `magick input.png -resize 1600x -quality 85 output.jpg`.
  2. Convert PNG screenshots to JPEG to shrink them dramatically.
  3. For GIFs, reduce frames/dimensions (`gifsicle --resize-width 800 --colors 128`) or convert to MP4 if the post allows video.
  4. If you legitimately need >20MB media, that cap is a library constant (MAX_IMAGE_SIZE_BYTES in clis/twitter/utils.js:21) — raise it only knowing X's own limits will still apply.

Example fix

// before
await postWithImage('fullpage.png'); // 34 MB
// after
execSync('magick fullpage.png -resize 2000x -quality 85 fullpage.jpg'); // ~2 MB
await postWithImage('fullpage.jpg');
Defensive patterns

Strategy: validation

Validate before calling

const MAX = 20 * 1024 * 1024;
const stat = fs.statSync(imagePath);
if (stat.size > MAX) {
  throw new Error(`${imagePath} is ${(stat.size / 1048576).toFixed(1)} MB; compress below 20 MB first`);
}

Try / catch

try {
  await postWithImage(imagePath);
} catch (err) {
  if (err instanceof ArgumentError && /Image too large/.test(err.message)) {
    const small = await compressImage(imagePath); // magick -resize/-quality
    return postWithImage(small);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a local image larger than 20MB: high-resolution PNG screenshots, long GIFs, uncompressed camera RAW-renamed files, or any stat.size exceeding 20*1024*1024 bytes.

Common situations: 4K/full-page screenshots saved as PNG (often >20MB); long animated GIFs; video files accidentally renamed to .gif; image downloads that were never compressed.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/4f0a552fa9a633f0. Report an issue: GitHub.