jackwener/OpenCLI · error · CommandExecutionError

Bilibili creator comparison returned a malformed manuscript

Error message

Bilibili creator comparison returned a malformed manuscript row

What it means

selectTarget iterates payload.data.list and whitelists each row: it must be a record with a string bvid matching /^BV[0-9A-Za-z]{10}$/. Because the endpoint is undocumented and unstable, any row failing that shape aborts the whole run with this CommandExecutionError instead of silently skipping bad rows. This is intentional strictness per the file's whitelist-based data contract.

Source

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

        ) {
            throw error;
        }
        const detail = `${error?.message ?? error} ${error?.hint ?? ''}`.trim();
        if (/abort|timed?\s*out|timeout/i.test(detail)) {
            throw new TimeoutError('Bilibili creator comparison', FETCH_TIMEOUT_SECONDS);
        }
        if (/HTTP\s+(401|403)|登录|passport|\/login\b/i.test(detail)) {
            throw new AuthRequiredError('member.bilibili.com', `Bilibili creator-center login is required: ${detail}`);
        }
        throw new CommandExecutionError(`Bilibili creator comparison request failed: ${detail}`);
    }
}

function selectTarget(list, bvid) {
    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;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the offending row (JSON.stringify the item) to see the new shape and update the whitelist validation
  2. Consider relaxing selectTarget to skip malformed rows instead of aborting, if Bilibili legitimately adds non-manuscript rows
  3. Update the bvid regex if Bilibili alters the BV ID format
  4. Refresh the sanitized probe fixtures and re-test against the live endpoint

Example fix

// before: abort on any bad row
if (!isRecord(item) || typeof item.bvid !== 'string' || !/^BV[0-9A-Za-z]{10}$/.test(item.bvid)) {
    throw new CommandExecutionError('...malformed manuscript row');
}

// after: skip non-conforming rows
if (!isRecord(item) || typeof item.bvid !== 'string' || !/^BV[0-9A-Za-z]{10}$/.test(item.bvid)) {
    continue;
}
Defensive patterns

Strategy: validation

Validate before calling

function rowLooksValid(item) {
  return item && typeof item === 'object' && !Array.isArray(item) && typeof item.bvid === 'string' && /^BV[0-9A-Za-z]{10}$/.test(item.bvid);
}
// fetch the list first and assert every row passes rowLooksValid before processing

Type guard

function isManuscriptRow(item) {
  return Boolean(item) && typeof item === 'object' && !Array.isArray(item) && typeof item.bvid === 'string' && /^BV[0-9A-Za-z]{10}$/.test(item.bvid);
}

Try / catch

try {
  const rows = await runCommand();
} catch (e) {
  if (/malformed manuscript row/.test(e.message)) {
    // log raw list JSON; either update the whitelist or relax to skip-and-continue
  } else throw e;
}

Prevention

When it happens

Trigger: The compare endpoint's list contains an entry that is not an object, lacks bvid, or whose bvid is not a 12-char 'BV'+10 alphanumeric string (e.g. Bilibili adding ad/promoted rows, new row types, or a schema change).

Common situations: Bilibili injecting non-manuscript rows (ads, recommendations) into the analytics list; a schema change introducing new row shapes; fixtures drifting from production payloads.

Understand the failure class

Related errors


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