jackwener/OpenCLI · error · CommandExecutionError

Xiaoyuzhou history row ${rowNumber} is missing podcast or fi

Error message

Xiaoyuzhou history row ${rowNumber} is missing podcast or finished state

What it means

After confirming the row has an episode object, parseHistoryEpisode requires `episode.podcast` to be a record (its title is read later) and `episode.isFinished` to be a boolean. Missing either means the library cannot render the podcast column or the finished state, so it throws CommandExecutionError.

Source

Thrown at clis/xiaoyuzhou/history.js:93

        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)) {
        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();

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry — if the API intermittently returns partial episode objects, a fresh run may be complete.
  2. Update the CLI/library to match any API field rename or type change (e.g. isFinished becoming an int).
  3. Inspect the raw episode object to confirm whether podcast metadata or isFinished is absent vs. retyped.
  4. Check the specific episode/podcast in the app — unpublished podcasts cause missing metadata rows.
  5. Patch the parser (or report upstream) to coerce 0/1 isFinished or handle missing podcast titles if the API legitimately allows them.

Example fix

// before
{ "episode": { "eid": "...", "isFinished": 1 } }
// after
{ "episode": { "eid": "...", "podcast": { "title": "P" }, "isFinished": true } }
Defensive patterns

Strategy: type-guard

Validate before calling

function hasPodcastAndFinished(ep) { return ep !== null && typeof ep === 'object' && ep.podcast !== null && typeof ep.podcast === 'object' && typeof ep.isFinished === 'boolean'; }

Type guard

function isWellFormedEpisode(ep) { return typeof ep === 'object' && ep !== null && typeof ep.podcast === 'object' && ep.podcast !== null && typeof ep.isFinished === 'boolean'; }

Try / catch

try {
  const rows = await runHistory();
} catch (e) {
  if (/missing podcast or finished state/.test(e.message)) {
    // retry, or fall back to rows without finished-state filtering
  } else throw e;
}

Prevention

When it happens

Trigger: A history row's episode object omits `podcast` (or it is null/non-object) or omits `isFinished` (or it is 0/1/'true' instead of a boolean), during `xiaoyuzhou history` parsing.

Common situations: Podcast unpublished/deleted so its metadata is dropped; API version change renames isFinished or switches it to an integer flag; partial responses from a flaky network layer; old fixtures.

Related errors


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