{"record":{"id":"69489eb3d3bc2f2e","repo":"jackwener/OpenCLI","slug":"pixiv-novel-returned-malformed-series-id","errorCode":null,"errorMessage":"Pixiv novel returned malformed series ID","messagePattern":"Pixiv novel returned malformed series ID","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novel.js","lineNumber":39,"sourceCode":"  const userId = String(body.userId ?? '').trim();\n  if (!/^\\d+$/.test(novelId) || novelId !== id || !title || !userName || !/^\\d+$/.test(userId)) {\n    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);\n  }\n  return { payload: body, identity: { novelId, title, userName, userId } };\n}\n\nexport function novelRowFromBody(body, id) {\n  const normalized = requireNovelBody(body, id);\n  const b = normalized.payload;\n  const identity = normalized.identity;\n  if (b.seriesNavData != null && (Array.isArray(b.seriesNavData) || typeof b.seriesNavData !== 'object')) {\n    throw new CommandExecutionError('Pixiv novel returned malformed series metadata');\n  }\n  const series = b.seriesNavData || {};\n  const seriesId = b.seriesId ?? series.seriesId ?? '';\n  const seriesTitle = b.seriesTitle ?? series.title ?? '';\n  if (seriesId !== '' && !/^\\d+$/.test(String(seriesId))) {\n    throw new CommandExecutionError('Pixiv novel returned malformed series ID');\n  }\n  if (seriesTitle !== '' && typeof seriesTitle !== 'string') {\n    throw new CommandExecutionError('Pixiv novel returned malformed series title');\n  }\n  const seriesOrder = series.order ?? b.seriesContentOrder ?? '';\n  if (seriesOrder !== '' && (!Number.isSafeInteger(seriesOrder) || seriesOrder < 1)) {\n    throw new CommandExecutionError('Pixiv novel returned malformed series order');\n  }\n  return {\n    novel_id: identity.novelId,\n    title: identity.title,\n    author: identity.userName,\n    user_id: identity.userId,\n    series_id: seriesId === '' ? '' : String(seriesId),\n    series_title: seriesTitle || '',\n    series_order: seriesOrder,\n    words: optionalCount(b.wordCount, 'word count'),\n    characters: optionalCount(b.characterCount ?? b.textCount, 'character count'),","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novel.js#L21-L57","documentation":"novelRowFromBody requires that any non-empty series ID is a string of digits. If seriesId (from b.seriesId or seriesNavData.seriesId) is missing, empty, or contains non-numeric characters, the response's series identity is unusable for the novel_id/series_id columns and linking, so it throws CommandExecutionError.","triggerScenarios":"Pixiv returns seriesNavData.seriesId (or top-level seriesId) as a non-numeric string, null coerced oddly, an object, or a numeric-looking value with whitespace/sign/decimal characters; only triggers when seriesId !== ''.","commonSituations":"API returns localized/obfuscated IDs during an A/B rollout; a proxy rewrites fields; a fixture uses 'abc' or '12345.6' for seriesId; scraping an region-locked novel returns partial series data.","solutions":["Inspect the raw response and confirm the actual seriesId value for that novel","Retry with an authenticated/logged-in Pixiv session — partial series data often comes from restricted content","Update the CLI parser to handle the new ID format if Pixiv changed the schema","Sanitize/normalize the field (strip whitespace, take the numeric part) before calling novelRowFromBody"],"exampleFix":"// before\nconst seriesId = body.seriesNavData.seriesId; // \" 105889 \"\n// after\nconst seriesId = String(body.seriesNavData.seriesId ?? '').trim();","handlingStrategy":"validation","validationCode":"const sid = body?.seriesNavData?.seriesId;\nif (sid !== undefined && sid !== '' && !/^\\d+$/.test(String(sid))) {\n  throw new Error(`bad seriesId: ${sid}`);\n}","typeGuard":"function isDigitString(v) {\n  return typeof v === 'string' && /^\\d+$/.test(v);\n}","tryCatchPattern":"try {\n  const row = novelRowFromBody(body, id);\n} catch (e) {\n  if (e.message.includes('malformed series ID')) {\n    body.seriesId = String(body.seriesNavData?.seriesId ?? '').replace(/\\D/g, '');\n  } else throw e;\n}","preventionTips":["Trim and normalize IDs before validation","Keep fixtures in sync with the live API schema","Retry restricted content with an authenticated session to avoid partial series data"],"tags":["pixiv","validation","series-id","api-schema"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}