{"record":{"id":"6550b5dc5b4a031e","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-returned-an-empty-page-with-a-c","errorCode":null,"errorMessage":"Xiaoyuzhou history returned an empty page with a continuation cursor","messagePattern":"Xiaoyuzhou history returned an empty page with a continuation cursor","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":82,"sourceCode":"    let next;\n    if (Array.isArray(payload)) {\n        entries = payload;\n        next = response.raw?.loadMoreKey;\n    }\n    else if (isRecord(payload) && Array.isArray(payload.data)\n        && !Object.prototype.hasOwnProperty.call(response.raw, 'loadMoreKey')) {\n        entries = payload.data;\n        next = payload.loadMoreKey;\n    }\n    else {\n        throw new CommandExecutionError('Xiaoyuzhou history returned an unexpected response shape');\n    }\n    if (next == null || next === '') return { entries, next: null };\n    if (typeof next !== 'string' || !next.trim()) {\n        throw new CommandExecutionError('Xiaoyuzhou history returned an invalid loadMoreKey');\n    }\n    if (entries.length === 0) {\n        throw new CommandExecutionError('Xiaoyuzhou history returned an empty page with a continuation cursor');\n    }\n    return { entries, next: next.trim() };\n}\n\nfunction parseHistoryEpisode(entry, rowNumber) {\n    if (!isRecord(entry) || !isRecord(entry.episode)) {\n        throw new CommandExecutionError(`Xiaoyuzhou history row ${rowNumber} is malformed; expected episode metadata`);\n    }\n    const episode = entry.episode;\n    if (!isRecord(episode.podcast) || typeof episode.isFinished !== 'boolean') {\n        throw new CommandExecutionError(`Xiaoyuzhou history row ${rowNumber} is missing podcast or finished state`);\n    }\n    return {\n        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 }),","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L64-L100","documentation":"The parser treats a page carrying a loadMoreKey as a promise of more data, so an empty entries array alongside a continuation cursor is contradictory — continuing would loop forever or produce a duplicate-cursor error. The library fails fast with CommandExecutionError to protect the pagination loop invariant.","triggerScenarios":"A history page returns zero entries but still includes a non-empty loadMoreKey, encountered while paging through `xiaoyuzhou history` (especially with --all).","commonSituations":"The account's history was deleted/mutated between page requests so the snapshot shifted; the server has a bug at the end of the cursor chain; a very aggressive --max-pages interacts with a shifting server-side snapshot.","solutions":["Re-run the command; a shifting server snapshot is often transient on retry.","Reduce the page window (lower --limit or --max-pages) so fewer continuation requests are made before the snapshot changes.","Wait a few minutes (e.g. after mass deletions) so the server-side history stabilizes, then retry.","Update the CLI in case the API changed end-of-pagination semantics (cursor present but exhausted).","If reproducible, capture the raw page and report the server-side cursor bug."],"exampleFix":"// before (server bug)\n{ \"data\": [], \"loadMoreKey\": \"next-page\" }\n// after (expected)\n{ \"data\": [] }  // or entries with a loadMoreKey","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const rows = await runHistory();\n} catch (e) {\n  if (e.message === 'Xiaoyuzhou history returned an empty page with a continuation cursor') {\n    // wait and retry once with a smaller --limit; the snapshot likely shifted\n  } else throw e;\n}","preventionTips":["Avoid mutating history (mass deletions, multi-device playback) while a --all fetch runs","Use bounded --limit/--max-pages instead of open-ended pagination when possible","Retry once automatically; shifting snapshots are usually transient"],"tags":["pagination","api-response","invariant-violation","xiaoyuzhou"],"backgroundTag":"empty-page-with-continuation-cursor","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}