jackwener/OpenCLI · error · CommandExecutionError

Pixiv novel returned malformed series ID

Error message

Pixiv novel returned malformed series ID

What it means

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.

Source

Thrown at clis/pixiv/novel.js:39

  const userId = String(body.userId ?? '').trim();
  if (!/^\d+$/.test(novelId) || novelId !== id || !title || !userName || !/^\d+$/.test(userId)) {
    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);
  }
  return { payload: body, identity: { novelId, title, userName, userId } };
}

export function novelRowFromBody(body, id) {
  const normalized = requireNovelBody(body, id);
  const b = normalized.payload;
  const identity = normalized.identity;
  if (b.seriesNavData != null && (Array.isArray(b.seriesNavData) || typeof b.seriesNavData !== 'object')) {
    throw new CommandExecutionError('Pixiv novel returned malformed series metadata');
  }
  const series = b.seriesNavData || {};
  const seriesId = b.seriesId ?? series.seriesId ?? '';
  const seriesTitle = b.seriesTitle ?? series.title ?? '';
  if (seriesId !== '' && !/^\d+$/.test(String(seriesId))) {
    throw new CommandExecutionError('Pixiv novel returned malformed series ID');
  }
  if (seriesTitle !== '' && typeof seriesTitle !== 'string') {
    throw new CommandExecutionError('Pixiv novel returned malformed series title');
  }
  const seriesOrder = series.order ?? b.seriesContentOrder ?? '';
  if (seriesOrder !== '' && (!Number.isSafeInteger(seriesOrder) || seriesOrder < 1)) {
    throw new CommandExecutionError('Pixiv novel returned malformed series order');
  }
  return {
    novel_id: identity.novelId,
    title: identity.title,
    author: identity.userName,
    user_id: identity.userId,
    series_id: seriesId === '' ? '' : String(seriesId),
    series_title: seriesTitle || '',
    series_order: seriesOrder,
    words: optionalCount(b.wordCount, 'word count'),
    characters: optionalCount(b.characterCount ?? b.textCount, 'character count'),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw response and confirm the actual seriesId value for that novel
  2. Retry with an authenticated/logged-in Pixiv session — partial series data often comes from restricted content
  3. Update the CLI parser to handle the new ID format if Pixiv changed the schema
  4. Sanitize/normalize the field (strip whitespace, take the numeric part) before calling novelRowFromBody

Example fix

// before
const seriesId = body.seriesNavData.seriesId; // " 105889 "
// after
const seriesId = String(body.seriesNavData.seriesId ?? '').trim();
Defensive patterns

Strategy: validation

Validate before calling

const sid = body?.seriesNavData?.seriesId;
if (sid !== undefined && sid !== '' && !/^\d+$/.test(String(sid))) {
  throw new Error(`bad seriesId: ${sid}`);
}

Type guard

function isDigitString(v) {
  return typeof v === 'string' && /^\d+$/.test(v);
}

Try / catch

try {
  const row = novelRowFromBody(body, id);
} catch (e) {
  if (e.message.includes('malformed series ID')) {
    body.seriesId = String(body.seriesNavData?.seriesId ?? '').replace(/\D/g, '');
  } else throw e;
}

Prevention

When it happens

Trigger: 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 !== ''.

Common situations: 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.

Understand the failure class

Related errors


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