jackwener/OpenCLI · error · CommandExecutionError

Pixiv user novel item returned malformed ${label}

Error message

Pixiv user novel item returned malformed ${label}

What it means

optionalCount validates that a numeric count field on a Pixiv user-novel item is a safe, non-negative integer before the CLI renders it. When the value is present but not a safe non-negative integer (e.g. a string, float, negative number, or boolean), the library throws CommandExecutionError to fail fast on malformed upstream Pixiv API payloads rather than emit corrupted ranking output. It only fires when the value is neither null nor empty string, since those fall back to the fallback value.

Source

Thrown at clis/pixiv/novels.js:16

import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
import {
  BATCH_SIZE,
  normalizePixivPositiveInteger,
  pixivFetch,
  requirePixivId,
  requirePixivPayloadObject,
  requirePixivString,
} from './utils.js';
import { dateOnly, tagsToString } from './bookmark-utils.js';

function optionalCount(value, label, fallback = '') {
  if (value == null || value === '') return fallback;
  if (!Number.isSafeInteger(value) || value < 0) {
    throw new CommandExecutionError(`Pixiv user novel item returned malformed ${label}`);
  }
  return value;
}

function userNovelRow(work, rank, expectedId) {
  const item = requirePixivPayloadObject(work, 'Pixiv user novel item');
  const id = requirePixivId(item.id, 'Pixiv user novel item');
  if (id !== expectedId) {
    throw new CommandExecutionError(`Pixiv user novels returned mismatched novel detail payload for ${expectedId}`);
  }
  const title = requirePixivString(item.title, 'Pixiv user novel item');
  return {
    rank,
    title,
    novel_id: id,
    words: optionalCount(item.wordCount, 'word count'),
    characters: optionalCount(item.textCount ?? item.characterCount, 'character count'),
    bookmarks: optionalCount(item.bookmarkCount, 'bookmark count', 0),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw payload item and the offending field to identify which count field has the wrong type
  2. Coerce the value before calling: Number.isFinite(+value) ? Number(value) : fallback, or pass value through a Number() cast if the API legitimately sends strings
  3. Upgrade/patch the CLI's optionalCount to accept numeric strings via /^\d+$/ before Number.isSafeInteger
  4. If Pixiv itself is returning malformed data, retry the request later or report the schema drift

Example fix

// before
const words = optionalCount(item.total_words, 'total_words');
// after
const rawWords = item.total_words;
const words = optionalCount(
  typeof rawWords === 'string' && /^\d+$/.test(rawWords) ? Number(rawWords) : rawWords,
  'total_words'
);
Defensive patterns

Strategy: validation

Validate before calling

function isNonNegSafeInt(v) { return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0; }
if (item.total_words != null && item.total_words !== '' && !isNonNegSafeInt(item.total_words)) {
  throw new Error('Skipping novel: malformed total_words');
}

Type guard

function isCount(v) {
  return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
}
function asCount(v, fallback = 0) {
  if (typeof v === 'string' && /^\d+$/.test(v)) return Number(v);
  return isCount(v) ? v : fallback;
}

Try / catch

try {
  row = userNovelRow(work, rank, expectedId);
} catch (err) {
  if (String(err.message).includes('malformed')) {
    console.warn(`Skipping rank ${rank}: ${err.message}`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: Pixiv's user novel detail endpoint returns a numeric field (like total_words, marker_count, or series count) as a string ('1234'), a float (12.5), a negative number, or another non-safe-integer type inside a works[] item passed to userNovelRow.

Common situations: Pixiv API schema changes or undocumented field types (numbers serialized as strings); proxies/caches re-serializing JSON with type coercion; a user-supplied expected id or scraped profile payload feeding the CLI with hand-edited data; new API fields no longer matching the CLI's assumptions.

Understand the failure class

Related errors


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