{"record":{"id":"a7d8c7fe033e0b90","repo":"jackwener/OpenCLI","slug":"no-images-found-for-illustration-row-illust-id","errorCode":null,"errorMessage":"No images found for illustration ${row.illust_id}.","messagePattern":"No images found for illustration (.+?)\\.","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"warning","filePath":"clis/pixiv/bookmark-download.js","lineNumber":58,"sourceCode":"    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}.`);\n  }\n  const files = pages.map((entry, index) => {\n    if (!entry || Array.isArray(entry) || typeof entry !== 'object' || !entry.urls || Array.isArray(entry.urls) || typeof entry.urls !== 'object') {\n      throw new CommandExecutionError(`Pixiv illustration ${row.illust_id} returned malformed page ${index + 1}`);\n    }\n    const parsed = parsePixivImageUrl(entry.urls.original || entry.urls.regular, `Pixiv illustration ${row.illust_id} page ${index + 1}`);\n    return {\n      ...parsed,\n      filename: `${row.illust_id}_p${index}${parsed.extension}`,\n    };\n  });\n  const finalPath = path.join(outputRoot, 'illust', row.illust_id);\n  if (pixivPathEntryExists(finalPath)) {\n    throw new CommandExecutionError(`Refusing to overwrite existing Pixiv download: ${finalPath}`);\n  }\n  const createdDirs = [];\n  for (let cursor = path.dirname(finalPath); !fs.existsSync(cursor); cursor = path.dirname(cursor)) {\n    createdDirs.push(cursor);","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/bookmark-download.js#L40-L76","documentation":"After confirming the pages payload is an array, prepareIllustPlan checks that at least one page exists. If the array is empty — the illustration has no downloadable pages — the CLI throws EmptyResultError('pixiv bookmark-download', ...) so the caller can treat it as an expected 'nothing found' outcome rather than a crash. This is a deliberate, structured empty-result signal, distinct from the malformed-payload error.","triggerScenarios":"/ajax/illust/{illust_id}/pages returns [] — the illustration exists but exposes zero pages (e.g. works deleted/restricted after being bookmarked, R-18 works hidden by safe-mode/filter settings, or an id that resolves to an empty gallery).","commonSituations":"A bookmarked work was deleted or made private by the artist; Pixiv safe-search/filtering (R-18 filter) strips the page data; the wrong kind of id was passed (a user or novel id instead of an illust id) yielding an empty page list; regional or age-restricted content requiring different auth.","solutions":["Verify the illust_id directly in a browser while logged in; if the work is deleted/private, remove it from the bookmark set and re-run","Disable R-18 filtering / safe mode on the account whose session the CLI uses, since filtered works return empty page lists","Confirm the id is an illustration id (numeric illust_id, not user/novel id)","Re-authenticate with fresh cookies — restricted-session responses can come back as empty arrays","Wrap this expected case: catch EmptyResultError and skip/log the illustration instead of failing the whole run"],"exampleFix":"// before\nawait bookmarkDownload({ illustId: row.illust_id });\n// after\ntry {\n  await bookmarkDownload({ illustId: row.illust_id });\n} catch (err) {\n  if (err instanceof EmptyResultError) { console.warn(`skipped ${row.illust_id}: no images`); return; }\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check that the work still exists and has pages before the run\nconst pages = await pixivFetch(page, `/ajax/illust/${row.illust_id}/pages`, {});\nif (Array.isArray(pages) && pages.length === 0) console.warn(`illust ${row.illust_id} has no pages; skipping`);","typeGuard":"function hasPages(pages) {\n  return Array.isArray(pages) && pages.length > 0;\n}","tryCatchPattern":"try {\n  await bookmarkDownload(row);\n} catch (err) {\n  if (err instanceof EmptyResultError) {\n    console.warn(`${err.scope}: ${err.message} — skipping`); // expected outcome, not a failure\n    return;\n  }\n  throw err;\n}","preventionTips":["Periodically prune bookmarks whose works have been deleted or made private","Disable R-18 filtering/safe mode on the account used for downloads","Check ids are valid illust ids (not user or novel ids) before running","Treat EmptyResultError as a skip-able event in batch scripts rather than a hard failure"],"tags":["empty-result","pixiv","expected-condition"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}