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
- Re-run the command after a few minutes to let Bilibili finish generating analytics for the manuscript.
- Re-login to the creator center so the session/cookie matches a healthy member.bilibili.com API response.
- Check the archive type of the bvid; if it is charging-exclusive or a course, the comparison endpoint may not carry stats for it.
- 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
- Only query manuscripts that already show analytics in the creator-center web UI
- Re-login periodically to keep member.bilibili.com sessions healthy
- Watch for Bilibili creator-center API changes before bulk querying
- Handle per-video failures gracefully instead of failing whole batches
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Bilibili ${label} API returned malformed top_replies
- Bilibili creator comparison returned malformed metric ${defi
- Bilibili creator comparison returned non-integer metric ${de
- Bilibili view API returned malformed paid-content metadata
- ${label} returned a malformed payload
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/fcffaebb5668b585.
Report an issue: GitHub.