jackwener/OpenCLI · error · CommandExecutionError

${label} returned a malformed image URL

Error message

${label} returned a malformed image URL

What it means

This CommandExecutionError is thrown by parsePixivImageUrl in clis/pixiv/bookmark-download.js:36-41 when the image URL string from the Pixiv API cannot be parsed by the URL constructor. The value is a non-empty string but not a valid absolute URL (new URL(value) throws).

Source

Thrown at clis/pixiv/bookmark-download.js:40

  ['.gif', 'image/gif'],
  ['.webp', 'image/webp'],
]);

function requireExecute(value) {
  if (value !== true) {
    throw new ArgumentError('Refusing to write local Pixiv downloads: pass --execute');
  }
}

function parsePixivImageUrl(value, label) {
  if (typeof value !== 'string' || !value) {
    throw new CommandExecutionError(`${label} returned a missing image URL`);
  }
  let url;
  try {
    url = new URL(value);
  } catch {
    throw new CommandExecutionError(`${label} returned a malformed image URL`);
  }
  const extension = path.extname(url.pathname).toLowerCase();
  const contentType = IMAGE_CONTENT_TYPES.get(extension);
  if (url.protocol !== 'https:' || url.hostname !== 'i.pximg.net' || url.username || url.password || url.port || !contentType) {
    throw new CommandExecutionError(`${label} returned an untrusted Pixiv image URL`);
  }
  return { url: url.href, extension, contentType };
}

async function prepareIllustPlan(page, row, outputRoot) {
  const pages = await pixivFetch(page, `/ajax/illust/${row.illust_id}/pages`, {
    notFoundMsg: `Illustration not found: ${row.illust_id}`,
  });
  if (!Array.isArray(pages)) {
    throw new CommandExecutionError('Pixiv pages API returned malformed payload');
  }
  if (pages.length === 0) {
    throw new EmptyResultError('pixiv bookmark-download', `No images found for illustration ${row.illust_id}.`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw pages API payload for the illustration to see what the url field actually contains
  2. If the value is protocol-relative or relative, normalize it against https://i.pximg.net before parsing
  3. Re-authenticate or verify the illustration is accessible — restricted works can return placeholder values
  4. Update the CLI to handle the new Pixiv response format if the API schema changed
Defensive patterns

Strategy: type-guard

Validate before calling

function looksLikeAbsoluteUrl(value) {
  if (typeof value !== 'string' || !value) return false;
  try { new URL(value); return true; } catch { return false; }
}
const raw = entry.urls.original || entry.urls.regular;
if (!looksLikeAbsoluteUrl(raw)) {
  console.warn('Non-absolute URL from API:', raw);
}

Type guard

function isValidUrl(value) {
  if (typeof value !== 'string' || !value) return false;
  try { new URL(value); return true; } catch { return false; }
}

Try / catch

try {
  const parsed = parsePixivImageUrl(value, label);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('malformed image URL')) {
    // Normalize protocol-relative or relative paths before retrying
    const absolute = value.startsWith('//') ? 'https:' + value : new URL(value, 'https://i.pximg.net/').href;
    parsed = parsePixivImageUrl(absolute, label);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: The urls.original or urls.regular field from the /ajax/illust/<id>/pages response is a non-URL string (e.g. a relative path, placeholder text, or HTML fragment), so new URL(value) throws at clis/pixiv/bookmark-download.js:38.

Common situations: Pixiv API returning relative or protocol-relative paths after a schema change; a proxy/mirror or cached response with corrupted fields; scraping-style integrations passing non-URL strings into the parser; localized error text landing in the url field on restricted works.

Understand the failure class

Related errors


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