jackwener/OpenCLI · error · CommandExecutionError

Xiaoyuzhou history row ${rowNumber} is malformed; expected e

Error message

Xiaoyuzhou history row ${rowNumber} is malformed; expected episode metadata

What it means

Each history row must be an object containing an `episode` object. parseHistoryEpisode throws this error when an entry is not a record or lacks episode metadata, since every downstream field (eid, pid, title, podcast, duration, pubDate) reads from entry.episode.

Source

Thrown at clis/xiaoyuzhou/history.js:89

        entries = payload.data;
        next = payload.loadMoreKey;
    }
    else {
        throw new CommandExecutionError('Xiaoyuzhou history returned an unexpected response shape');
    }
    if (next == null || next === '') return { entries, next: null };
    if (typeof next !== 'string' || !next.trim()) {
        throw new CommandExecutionError('Xiaoyuzhou history returned an invalid loadMoreKey');
    }
    if (entries.length === 0) {
        throw new CommandExecutionError('Xiaoyuzhou history returned an empty page with a continuation cursor');
    }
    return { entries, next: next.trim() };
}

function parseHistoryEpisode(entry, rowNumber) {
    if (!isRecord(entry) || !isRecord(entry.episode)) {
        throw new CommandExecutionError(`Xiaoyuzhou history row ${rowNumber} is malformed; expected episode metadata`);
    }
    const episode = entry.episode;
    if (!isRecord(episode.podcast) || typeof episode.isFinished !== 'boolean') {
        throw new CommandExecutionError(`Xiaoyuzhou history row ${rowNumber} is missing podcast or finished state`);
    }
    return {
        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)) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command to see if the malformed row is transient.
  2. Update the CLI/library in case the API changed the history row envelope.
  3. Check whether the affected episode was deleted — deleted episodes can yield stub rows; a newer parser version may skip them.
  4. Inspect the raw page payload to confirm which row and shape was returned.
  5. If reproducible, report the row shape upstream so the parser can handle tombstones.

Example fix

// before (deleted-episode tombstone)
{ "eid": "...", "episode": null }
// after
{ "eid": "...", "episode": { "eid": "...", "pid": "...", "title": "Ep 1", "podcast": { "title": "P" }, "isFinished": false } }
Defensive patterns

Strategy: validation

Validate before calling

function hasEpisodeMeta(entry) { return entry !== null && typeof entry === 'object' && !Array.isArray(entry) && entry.episode !== null && typeof entry.episode === 'object'; }

Type guard

function isEpisodeEntry(v) { return typeof v === 'object' && v !== null && typeof v.episode === 'object' && v.episode !== null; }

Try / catch

try {
  const rows = await runHistory();
} catch (e) {
  const m = e.message.match(/row (\d+) is malformed/);
  if (m) { /* skip/retry, or log the offending row index */ } else throw e;
}

Prevention

When it happens

Trigger: During `xiaoyuzhou history`, one of the entries in the page's data array is null, an array, a scalar, or an object without an `episode` key (e.g. the API returned a tombstone/deleted-episode placeholder).

Common situations: An episode was deleted from its podcast so the API returns a stub row; API version change nests metadata differently; a proxy strips fields; stale mock fixtures.

Understand the failure class

Related errors


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