jackwener/OpenCLI · error · ArgumentError
Invalid image URL: ${imageUrl}
Error message
Invalid image URL: ${imageUrl} What it means
downloadRemoteImage first parses the given URL with the URL constructor; if parsing fails it throws ArgumentError `Invalid image URL: <input>`. This catches syntactically malformed URLs before any network request is made.
Source
Thrown at clis/twitter/utils.js:90
}
throw new ArgumentError(
`Unsupported remote image format "${normalizedContentType || 'unknown'}". Supported: jpg, jpeg, png, gif, webp`,
);
}
/**
* 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 });View on GitHub (pinned to 49907e53dc)
Solutions
- Ensure the URL includes the scheme: prepend https:// if missing.
- Encode the URL: wrap in encodeURI() or quote it in the shell to protect ?& and spaces.
- Confirm you are passing a URL, not a local file path — local paths belong to resolveImagePath.
- Sanity-check in Node: `new URL(value)` in a REPL should not throw before calling the API.
Example fix
// before
await downloadRemoteImage('cdn.example.com/cat.png'); // no scheme
// after
const url = raw.startsWith('http') ? raw : `https://${raw}`;
await downloadRemoteImage(encodeURI(url)); Defensive patterns
Strategy: validation
Validate before calling
let parsed;
try { parsed = new URL(imageUrl); } catch {
throw new Error(`Malformed image URL: ${JSON.stringify(imageUrl)}`);
}
if (!parsed.protocol.startsWith('http')) {
throw new Error(`Scheme must be http(s): ${imageUrl}`);
} Type guard
function isHttpUrl(value) {
if (typeof value !== 'string' || value.length === 0) return false;
try { return new URL(value).protocol.startsWith('http'); } catch { return false; }
} Try / catch
try {
await downloadRemoteImage(imageUrl);
} catch (err) {
if (err instanceof ArgumentError && err.message.startsWith('Invalid image URL')) {
return downloadRemoteImage(new URL(imageUrl, 'https://example.com').href); // resolve relative
}
throw err;
} Prevention
- Prepend https:// when the scheme is missing.
- Quote URLs in the shell and encode query strings/spaces (encodeURI).
- Distinguish local paths vs URLs in your CLI arg handling before dispatching.
When it happens
Trigger: Passing a string that `new URL()` cannot parse: missing scheme ("example.com/img.png"), spaces or unencoded special characters, empty string, a file path mistakenly passed to the remote-image path, or undefined/null coerced to a string.
Common situations: Copy-pasting a URL that lost its https:// prefix; shell mangling of '&', '?', or spaces without quotes; forgetting to download a file and passing its local path to the URL-based API; template-string variables that interpolated to empty.
Related errors
- Unsupported Nowcoder URL; expected /discuss/<content-id> or
- nuget ${label} must be a positive integer
- Invalid board URL: ${trimmed}
- --adults must be an integer between 1 and 9, got ${JSON.stri
- tvmaze ${label} must be a positive integer
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/8734665a318893ce.
Report an issue: GitHub.