{"record":{"id":"7725dc74384d5a9a","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-playback-progress-row-index-1-is-m","errorCode":null,"errorMessage":"Xiaoyuzhou playback progress row ${index + 1} is malformed","messagePattern":"Xiaoyuzhou playback progress row (.+?) is malformed","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":114,"sourceCode":"        eid: requiredId(episode.eid, `eid in row ${rowNumber}`),\n        pid: requiredId(episode.pid, `pid in row ${rowNumber}`),\n        title: requiredString(episode.title, `title in row ${rowNumber}`),\n        podcast: requiredString(episode.podcast.title, `podcast title in row ${rowNumber}`),\n        durationSec: optionalSeconds(episode.duration, `duration in row ${rowNumber}`, { positive: true }),\n        pubDate: optionalIsoTime(episode.pubDate, `pubDate in row ${rowNumber}`, { required: true }),\n        finished: episode.isFinished,\n    };\n}\n\nfunction parseProgressRows(response, episodes) {\n    if (!Array.isArray(response?.data)) {\n        throw new CommandExecutionError('Xiaoyuzhou playback progress returned an unexpected response shape');\n    }\n    const requested = new Map(episodes.map((episode) => [episode.eid, episode]));\n    const progressById = new Map();\n    for (const [index, row] of response.data.entries()) {\n        if (!isRecord(row)) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress row ${index + 1} is malformed`);\n        }\n        const eid = requiredId(row.eid, `progress eid in row ${index + 1}`);\n        const episode = requested.get(eid);\n        if (!episode) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress returned unrequested eid ${eid}`);\n        }\n        if (progressById.has(eid)) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress returned duplicate eid ${eid}`);\n        }\n        const pid = requiredId(row.pid, `progress pid in row ${index + 1}`);\n        if (pid !== episode.pid) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress pid did not match history eid ${eid}`);\n        }\n        const progressSec = optionalSeconds(row.progress, `progress in row ${index + 1}`);\n        if (progressSec !== null && episode.durationSec !== null && progressSec > episode.durationSec) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress exceeded duration for eid ${eid}`);\n        }\n        progressById.set(eid, {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L96-L132","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{ \"data\": [ null, { \"eid\": \"...\" } ] }\n// after\n{ \"data\": [ { \"eid\": \"...\", \"pid\": \"...\", \"progress\": 0 } ] }","handlingStrategy":"type-guard","validationCode":"function allRowsAreObjects(res) { return Array.isArray(res?.data) && res.data.every(r => r !== null && typeof r === 'object' && !Array.isArray(r)); }","typeGuard":"function isProgressRow(v) { return typeof v === 'object' && v !== null && !Array.isArray(v); }","tryCatchPattern":"try {\n  const rows = await runHistory();\n} catch (e) {\n  const m = e.message.match(/progress row (\\d+) is malformed/);\n  if (m) { /* retry or reduce --limit to isolate the bad row */ } else throw e;\n}","preventionTips":["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"],"tags":["api-response","schema-validation","xiaoyuzhou"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}