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

  1. Retry the command — transient corruption or a flaky backend row often resolves on a fresh call.
  2. Update the CLI/library in case the row schema changed.
  3. Inspect the raw progress payload and locate the offending row index to see what the server actually sent.
  4. Narrow the failing set (lower --limit) and retry to isolate whether a specific episode's progress row is the problem.
  5. 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

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

Related errors


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