jackwener/OpenCLI · error · CommandExecutionError

Too many images: ${paths.length} (max ${MAX_IMAGES})

Error message

Too many images: ${paths.length} (max ${MAX_IMAGES})

What it means

CommandExecutionError thrown by validateImagePaths when a comma-separated image path list contains more than MAX_IMAGES (4) entries. X.com allows at most 4 images per post, so the CLI enforces this limit before opening the composer to avoid a guaranteed upload failure. Raised in validateImagePaths, called via absPaths before any browser interaction.

Source

Thrown at clis/twitter/post.js:22

import { CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
import { unwrapBrowserResult } from './shared.js';
import { isRecoverableFileInputError } from './utils.js';

const MAX_IMAGES = 4;
const UPLOAD_POLL_MS = 500;
const UPLOAD_TIMEOUT_MS = 30_000;
const COMPOSER_POLL_MS = 250;
const COMPOSER_TIMEOUT_MS = 10_000;
const SUBMIT_POLL_MS = 500;
const SUBMIT_TIMEOUT_MS = 15_000;
const COMPOSE_URL = 'https://x.com/compose/post';
const FILE_INPUT_SELECTOR = 'input[type="file"][data-testid="fileInput"]';
const SUPPORTED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp']);

function validateImagePaths(raw) {
    const paths = raw.split(',').map(s => s.trim()).filter(Boolean);
    if (paths.length > MAX_IMAGES) {
        throw new CommandExecutionError(`Too many images: ${paths.length} (max ${MAX_IMAGES})`);
    }
    return paths.map(p => {
        const absPath = path.resolve(p);
        const ext = path.extname(absPath).toLowerCase();
        if (!SUPPORTED_EXTENSIONS.has(ext)) {
            throw new CommandExecutionError(`Unsupported image format "${ext}". Supported: jpg, png, gif, webp`);
        }
        const stat = fs.statSync(absPath, { throwIfNoEntry: false });
        if (!stat || !stat.isFile()) {
            throw new CommandExecutionError(`Not a valid file: ${absPath}`);
        }
        return absPath;
    });
}

function isUnsupportedInsertTextError(err) {
    const msg = err instanceof Error ? err.message : String(err);
    const lower = msg.toLowerCase();

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Reduce the comma-separated list to at most 4 image paths.
  2. Split posts into multiple tweets if you need more than 4 images (each with its own set).
  3. In scripts, slice the file list to 4 before invoking: files.slice(0, 4).join(',').
  4. Consider a video post instead — X's video path has different (larger) limits.

Example fix

// before
const images = allFiles.join(',');
await post({ text, images }); // may exceed 4
// after
const images = allFiles.slice(0, 4).join(',');
if (allFiles.length > 4) console.warn('Posting only first 4 images; Twitter allows max 4 per post');
await post({ text, images });
Defensive patterns

Strategy: validation

Validate before calling

const paths = rawImages.split(',').map(s => s.trim()).filter(Boolean);
if (paths.length > 4) throw new Error(`Max 4 images per post, got ${paths.length}`);

Type guard

function isWithinImageLimit(paths, max = 4) {
  return Array.isArray(paths) && paths.length <= max;
}

Try / catch

try {
  await run(['twitter', 'post', '--images', rawImages]);
} catch (err) {
  if (err.message.startsWith('Too many images')) {
    const first4 = rawImages.split(',').map(s => s.trim()).filter(Boolean).slice(0, 4).join(',');
    await run(['twitter', 'post', '--images', first4]);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the twitter post command with an --images/-style comma-joined string containing 5+ paths, e.g. 'a.jpg,b.png,c.jpg,d.png,e.jpg' — after trim/filter the array length exceeds 4.

Common situations: Globbing directories into a comma list and exceeding the limit; scripts joining all files in a folder; users unaware of Twitter's 4-image cap; trailing-comma strings that leave extra entries after filtering.

Related errors


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