{"record":{"id":"e147f029e5b7236d","repo":"jackwener/OpenCLI","slug":"pixiv-user-novel-details-returned-malformed-works","errorCode":null,"errorMessage":"Pixiv user novel details returned malformed works payload","messagePattern":"Pixiv user novel details returned malformed works payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novels.js","lineNumber":57,"sourceCode":"}\n\nfunction requireProfileNovelIds(body) {\n  const payload = requirePixivPayloadObject(body, 'Pixiv user profile');\n  if (!payload.novels || Array.isArray(payload.novels) || typeof payload.novels !== 'object') {\n    throw new CommandExecutionError('Pixiv user profile returned malformed novels payload');\n  }\n  const ids = Object.keys(payload.novels);\n  const invalid = ids.find(id => !/^\\d+$/.test(id));\n  if (invalid) {\n    throw new CommandExecutionError(`Pixiv user profile returned malformed novel ID: ${invalid}`);\n  }\n  return ids;\n}\n\nfunction requireDetailWorks(body) {\n  const payload = requirePixivPayloadObject(body, 'Pixiv user novel details');\n  if (!payload.works || Array.isArray(payload.works) || typeof payload.works !== 'object') {\n    throw new CommandExecutionError('Pixiv user novel details returned malformed works payload');\n  }\n  return payload.works;\n}\n\ncli({\n  site: 'pixiv',\n  name: 'novels',\n  access: 'read',\n  description: \"List a Pixiv user's novels\",\n  domain: 'www.pixiv.net',\n  strategy: Strategy.COOKIE,\n  args: [\n    { name: 'user-id', positional: true, required: true, help: 'Pixiv user ID' },\n    { name: 'limit', type: 'int', default: 20, help: 'Number of results' },\n  ],\n  columns: ['rank', 'title', 'novel_id', 'words', 'characters', 'bookmarks', 'tags', 'created', 'url'],\n  func: async (page, kwargs) => {\n    const userId = String(kwargs['user-id'] ?? '').trim();","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novels.js#L39-L75","documentation":"requireDetailWorks validates the 'Pixiv user novel details' response: works must be a non-null, non-array object (map). Missing/null works, an array, or a primitive triggers this throw because the detail step cannot proceed without a works map to iterate for userNovelRow. It's a structural gate protecting the CLI from upstream payload shape drift or failed requests parsed as success.","triggerScenarios":"The user-novel detail endpoint returns {works: null}, {works: []}, {works: '...'}, or omits works — typically when the request failed silently, the novels in the profile had no accessible details, or the API response schema changed.","commonSituations":"Expired Pixiv session/cookies yielding an error body; rate limiting returning partial JSON; users whose novels are all private so details come back empty/array-shaped; library version vs current Pixiv API mismatch.","solutions":["Inspect the raw HTTP response body and status code to distinguish auth/rate-limit failures from shape drift","Re-authenticate or refresh Pixiv session cookies if the body indicates login required","Handle the empty case: treat missing/array-shaped works as 'no details' and skip instead of throwing","Retry with backoff if the response was truncated or rate-limited","Patch requireDetailWorks to normalize arrays to maps keyed by work id"],"exampleFix":"// before\nconst works = requireDetailWorks(body);\n// after\nif (body && Array.isArray(body.works)) {\n  body.works = Object.fromEntries(body.works.map(w => [String(w.id), w]));\n}\nconst works = requireDetailWorks(body);","handlingStrategy":"try-catch","validationCode":"function hasValidWorksMap(body) {\n  return body != null &&\n    typeof body.works === 'object' && body.works !== null &&\n    !Array.isArray(body.works);\n}\nif (!hasValidWorksMap(body)) throw new Error('Novel details response unusable: works is not an object map');","typeGuard":"function hasWorksMap(p) {\n  return typeof p === 'object' && p !== null &&\n    typeof p.works === 'object' && p.works !== null && !Array.isArray(p.works);\n}","tryCatchPattern":"try {\n  works = requireDetailWorks(body);\n} catch (err) {\n  if (String(err.message).includes('malformed works payload')) {\n    console.warn('No works map in details response; skipping this batch');\n    works = {};\n  } else throw err;\n}","preventionTips":["Verify HTTP status/auth before parsing detail responses; refresh Pixiv cookies on 4xx bodies","Retry with backoff on truncated or rate-limited responses","Normalize array-shaped works to a keyed map before validation","Test the parser against captured Pixiv detail fixtures to detect schema drift"],"tags":["pixiv","cli","schema-validation","api-payload"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}