jackwener/OpenCLI · error · CommandExecutionError
Xiaoyuzhou playback progress row ${index + 1} is malformed
Error message
Xiaoyuzhou playback progress row ${index + 1} is malformed What it means
Each row inside response.data of the playback-progress response must be an object (fields eid, pid, progress, playedAt are read from it). A null/array/scalar row is unparseable, so parseProgressRows throws CommandExecutionError identifying the row by 1-based index.
Source
Thrown at clis/xiaoyuzhou/history.js:114
eid: requiredId(episode.eid, `eid in row ${rowNumber}`),
pid: requiredId(episode.pid, `pid in row ${rowNumber}`),
title: requiredString(episode.title, `title in row ${rowNumber}`),
podcast: requiredString(episode.podcast.title, `podcast title in row ${rowNumber}`),
durationSec: optionalSeconds(episode.duration, `duration in row ${rowNumber}`, { positive: true }),
pubDate: optionalIsoTime(episode.pubDate, `pubDate in row ${rowNumber}`, { required: true }),
finished: episode.isFinished,
};
}
function parseProgressRows(response, episodes) {
if (!Array.isArray(response?.data)) {
throw new CommandExecutionError('Xiaoyuzhou playback progress returned an unexpected response shape');
}
const requested = new Map(episodes.map((episode) => [episode.eid, episode]));
const progressById = new Map();
for (const [index, row] of response.data.entries()) {
if (!isRecord(row)) {
throw new CommandExecutionError(`Xiaoyuzhou playback progress row ${index + 1} is malformed`);
}
const eid = requiredId(row.eid, `progress eid in row ${index + 1}`);
const episode = requested.get(eid);
if (!episode) {
throw new CommandExecutionError(`Xiaoyuzhou playback progress returned unrequested eid ${eid}`);
}
if (progressById.has(eid)) {
throw new CommandExecutionError(`Xiaoyuzhou playback progress returned duplicate eid ${eid}`);
}
const pid = requiredId(row.pid, `progress pid in row ${index + 1}`);
if (pid !== episode.pid) {
throw new CommandExecutionError(`Xiaoyuzhou playback progress pid did not match history eid ${eid}`);
}
const progressSec = optionalSeconds(row.progress, `progress in row ${index + 1}`);
if (progressSec !== null && episode.durationSec !== null && progressSec > episode.durationSec) {
throw new CommandExecutionError(`Xiaoyuzhou playback progress exceeded duration for eid ${eid}`);
}
progressById.set(eid, {View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the command — transient corruption or a flaky backend row often resolves on a fresh call.
- Update the CLI/library in case the row schema changed.
- Inspect the raw progress payload and locate the offending row index to see what the server actually sent.
- Narrow the failing set (lower --limit) and retry to isolate whether a specific episode's progress row is the problem.
- Report the tombstone/null-row behavior upstream so the parser can skip or normalize it.
Example fix
// before
{ "data": [ null, { "eid": "..." } ] }
// after
{ "data": [ { "eid": "...", "pid": "...", "progress": 0 } ] } Defensive patterns
Strategy: type-guard
Validate before calling
function allRowsAreObjects(res) { return Array.isArray(res?.data) && res.data.every(r => r !== null && typeof r === 'object' && !Array.isArray(r)); } Type guard
function isProgressRow(v) { return typeof v === 'object' && v !== null && !Array.isArray(v); } Try / catch
try {
const rows = await runHistory();
} catch (e) {
const m = e.message.match(/progress row (\d+) is malformed/);
if (m) { /* retry or reduce --limit to isolate the bad row */ } else throw e;
} Prevention
- Retry transient corrupted responses before investigating further
- Update the CLI when the progress row schema changes
- Use smaller --limit values to reduce exposure to partial/corrupt pages
When it happens
Trigger: The progress endpoint's data array contains a null, an array, or a scalar element — e.g. the API emits null rows for episodes whose progress record was deleted, or a proxy mangles an element.
Common situations: Server-side tombstone rows for wiped progress; API version change adding envelope elements inside the array; truncated/corrupted response from a flaky network; stale test fixtures.
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
- Xiaoyuzhou history row ${rowNumber} is malformed; expected e
- Xiaoyuzhou history row ${rowNumber} is missing podcast or fi
- Bilibili ${label} API returned a malformed payload
- Bilibili ${label} API returned malformed data
- Bilibili ${label} API did not return replies
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7725dc74384d5a9a.
Report an issue: GitHub.