{"record":{"id":"c2c254bd6946936f","repo":"jackwener/OpenCLI","slug":"label-returned-a-malformed-image-url","errorCode":null,"errorMessage":"${label} returned a malformed image URL","messagePattern":"(.+?) returned a malformed image URL","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/bookmark-download.js","lineNumber":40,"sourceCode":"  ['.gif', 'image/gif'],\n  ['.webp', 'image/webp'],\n]);\n\nfunction requireExecute(value) {\n  if (value !== true) {\n    throw new ArgumentError('Refusing to write local Pixiv downloads: pass --execute');\n  }\n}\n\nfunction parsePixivImageUrl(value, label) {\n  if (typeof value !== 'string' || !value) {\n    throw new CommandExecutionError(`${label} returned a missing image URL`);\n  }\n  let url;\n  try {\n    url = new URL(value);\n  } catch {\n    throw new CommandExecutionError(`${label} returned a malformed image URL`);\n  }\n  const extension = path.extname(url.pathname).toLowerCase();\n  const contentType = IMAGE_CONTENT_TYPES.get(extension);\n  if (url.protocol !== 'https:' || url.hostname !== 'i.pximg.net' || url.username || url.password || url.port || !contentType) {\n    throw new CommandExecutionError(`${label} returned an untrusted Pixiv image URL`);\n  }\n  return { url: url.href, extension, contentType };\n}\n\nasync function prepareIllustPlan(page, row, outputRoot) {\n  const pages = await pixivFetch(page, `/ajax/illust/${row.illust_id}/pages`, {\n    notFoundMsg: `Illustration not found: ${row.illust_id}`,\n  });\n  if (!Array.isArray(pages)) {\n    throw new CommandExecutionError('Pixiv pages API returned malformed payload');\n  }\n  if (pages.length === 0) {\n    throw new EmptyResultError('pixiv bookmark-download', `No images found for illustration ${row.illust_id}.`);","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/bookmark-download.js#L22-L58","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the raw pages API payload for the illustration to see what the url field actually contains","If the value is protocol-relative or relative, normalize it against https://i.pximg.net before parsing","Re-authenticate or verify the illustration is accessible — restricted works can return placeholder values","Update the CLI to handle the new Pixiv response format if the API schema changed"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"function looksLikeAbsoluteUrl(value) {\n  if (typeof value !== 'string' || !value) return false;\n  try { new URL(value); return true; } catch { return false; }\n}\nconst raw = entry.urls.original || entry.urls.regular;\nif (!looksLikeAbsoluteUrl(raw)) {\n  console.warn('Non-absolute URL from API:', raw);\n}","typeGuard":"function isValidUrl(value) {\n  if (typeof value !== 'string' || !value) return false;\n  try { new URL(value); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  const parsed = parsePixivImageUrl(value, label);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('malformed image URL')) {\n    // Normalize protocol-relative or relative paths before retrying\n    const absolute = value.startsWith('//') ? 'https:' + value : new URL(value, 'https://i.pximg.net/').href;\n    parsed = parsePixivImageUrl(absolute, label);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Validate URLs from the API with new URL() before further processing","Normalize protocol-relative ('//...') or relative paths against https://i.pximg.net upstream of the parser","Log raw API payloads when a schema change is suspected","Re-authenticate when payloads look degraded or contain placeholder values"],"tags":["pixiv","url-parsing","api-response","malformed-data"],"backgroundTag":"malformed-url","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}