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
- Retry the command to see if the malformed row is transient.
- Update the CLI/library in case the API changed the history row envelope.
- Check whether the affected episode was deleted — deleted episodes can yield stub rows; a newer parser version may skip them.
- Inspect the raw page payload to confirm which row and shape was returned.
- 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
- Update the CLI so deleted-episode tombstones are handled by newer parsers
- Retry once; partial responses are often transient
- Inspect raw page payloads when a specific episode repeatedly fails
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
- 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 missing podcast or fi
- Xiaoyuzhou playback progress row ${index + 1} is malformed
- 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/610d8c52cb106664.
Report an issue: GitHub.