jackwener/OpenCLI · error · CommandExecutionError

Bilibili creator comparison returned malformed stat data for

Error message

Bilibili creator comparison returned malformed stat data for ${bvid}

What it means

selectTarget found the requested bvid among the creator-comparison rows, but the matched row's `stat` field is not a plain object (missing, null, or an array). Because the command reads every metric out of `target.stat`, it refuses to continue rather than fail later per-metric. This guards against undocumented contract drift in Bilibili's member.bilibili.com comparison endpoint.

Source

Thrown at clis/bilibili/creator-stats.js:112

    const matches = [];
    for (const item of list) {
        if (!isRecord(item) || typeof item.bvid !== 'string' || !/^BV[0-9A-Za-z]{10}$/.test(item.bvid)) {
            throw new CommandExecutionError('Bilibili creator comparison returned a malformed manuscript row');
        }
        if (item.bvid === bvid) matches.push(item);
    }
    if (matches.length > 1) {
        throw new CommandExecutionError(`Bilibili creator comparison returned duplicate rows for ${bvid}`);
    }
    if (matches.length === 0) {
        throw new EmptyResultError(
            `bilibili creator-stats ${bvid}`,
            'The manuscript was not present in the latest 100 creator analytics rows; it may be older, not owned by this account, or not analyzed yet.',
        );
    }
    const target = matches[0];
    if (!isRecord(target.stat)) {
        throw new CommandExecutionError(`Bilibili creator comparison returned malformed stat data for ${bvid}`);
    }
    return target;
}

function metricValue(target, definition) {
    const source = definition.source === 'target' ? target : target.stat;
    if (!Object.prototype.hasOwnProperty.call(source, definition.key)) {
        throw new CommandExecutionError(`Bilibili creator comparison omitted metric ${definition.key}`);
    }
    const raw = source[definition.key];
    if (raw === null) return null;
    if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {
        throw new CommandExecutionError(`Bilibili creator comparison returned malformed metric ${definition.key}`);
    }
    if (definition.unit === 'percent' && raw > 10_000) {
        throw new CommandExecutionError(`Bilibili creator comparison returned out-of-range percentage ${definition.key}`);
    }
    if ((definition.unit === 'count' || definition.unit === 'seconds') && !Number.isSafeInteger(raw)) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command after a few minutes to let Bilibili finish generating analytics for the manuscript.
  2. Re-login to the creator center so the session/cookie matches a healthy member.bilibili.com API response.
  3. Check the archive type of the bvid; if it is charging-exclusive or a course, the comparison endpoint may not carry stats for it.
  4. If reproducible, update the CLI's response parsing to the new API shape (verify with a raw request to the compare endpoint).

Example fix

// caller-side guard before relying on stat
const row = await selectTarget(list, bvid);
if (!row || typeof row !== 'object' || Array.isArray(row) || !row.stat || typeof row.stat !== 'object' || Array.isArray(row.stat)) {
    // fall back: skip stats for this bvid or retry later
}
Defensive patterns

Strategy: type-guard

Validate before calling

const isRecord = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
const row = list.find((item) => item?.bvid === bvid);
if (!isRecord(row?.stat)) { /* defer stats / retry later */ }

Type guard

const isRecord = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
function hasStat(row) { return isRecord(row) && isRecord(row.stat); }

Try / catch

try {
    const stats = await creatorStats(bvid);
} catch (e) {
    if (/malformed stat data/.test(e.message)) {
        // treat as no-analytics-yet: retry later or skip metrics
    } else throw e;
}

Prevention

When it happens

Trigger: Bilibili's /x/web/data/archive_diagnose/compare?size=100 returns a row for the bvid whose `stat` field is absent, null, or a non-object (e.g. API schema change, partial analytics data, or the row being a special entry type without stats).

Common situations: Bilibili silently changes the undocumented creator-center response shape; a newly published manuscript gets a row before analytics are generated; proxy/CDN returns partially stripped JSON; account has an unusual archive type (charging-exclusive, course) whose rows lack `stat`.

Understand the failure class

Related errors


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