{"record":{"id":"03aadb38fc138c63","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-archive-did-not-reach-the-end-o","errorCode":null,"errorMessage":"Xiaoyuzhou history archive did not reach the end of pagination","messagePattern":"Xiaoyuzhou history archive did not reach the end of pagination","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":230,"sourceCode":"            break;\n        }\n        if (page.next === loadMoreKey || seenCursors.has(page.next)) {\n            throw new CommandExecutionError('Xiaoyuzhou history pagination repeated the same cursor');\n        }\n        if (pageNumber === maxPages) {\n            throw new CommandExecutionError(\n                `Xiaoyuzhou history stopped at the --max-pages safety limit (${maxPages}) before reaching the end`,\n            );\n        }\n        seenCursors.add(page.next);\n        loadMoreKey = page.next;\n    }\n\n    if (rows.length === 0) {\n        throw new EmptyResultError('xiaoyuzhou history', 'The logged-in account has no playback history');\n    }\n    if (fetchAll && !exhausted) {\n        throw new CommandExecutionError('Xiaoyuzhou history archive did not reach the end of pagination');\n    }\n    return rows;\n}\n\ncli({\n    site: 'xiaoyuzhou',\n    name: 'history',\n    access: 'read',\n    description: 'List playback history for the logged-in Xiaoyuzhou account',\n    domain: 'api.xiaoyuzhoufm.com',\n    strategy: Strategy.LOCAL,\n    browser: false,\n    args: [\n        { name: 'limit', type: 'int', default: DEFAULT_LIMIT, help: `Maximum rows to return (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}). Ignored with --all.` },\n        { name: 'all', type: 'bool', default: false, help: 'Fetch every history page until the API cursor is exhausted.' },\n        { name: 'max-pages', type: 'int', default: DEFAULT_MAX_PAGES, help: `Pagination safety limit (default ${DEFAULT_MAX_PAGES}, max ${HARD_MAX_PAGES}).` },\n    ],\n    columns: ['rank', 'eid', 'pid', 'title', 'podcast', 'durationSec', 'progressSec', 'progressPct', 'playedAt', 'pubDate', 'finished', 'url'],","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L212-L248","documentation":"With --all set, fetchHistory promises the complete archive. If pagination ended without observing page.next === null — i.e. exhaustion was never confirmed — it throws rather than returning data that may be silently incomplete.","triggerScenarios":"A --all run stops while a next cursor still exists — e.g. the loop ends early after a swallowed transient error, an API returns an unexpected cursor shape so exhaustion is never detected, or cursor handling bugs make page.next never null.","commonSituations":"API changes the end-of-pagination signal (next becomes '' or a sentinel instead of null); --all runs interrupted by transient errors that a wrapper turns into a normal return; middleware truncating the final empty-next response.","solutions":["Inspect the final page's raw response to see how end-of-pagination is signaled and update the exhaustion check (e.g. treat '' or a sentinel as null).","Retry the full --all fetch; transient issues may resolve.","Fall back to --limit/--max-pages with incremental resumption if true exhaustion cannot be detected with the current API.","Verify no middleware/proxy is truncating the final response that carries the null next cursor."],"exampleFix":"// before\nif (page.next === null) { exhausted = true; break; }\n// after (API signals end with empty string)\nif (page.next === null || page.next === '' || page.next === undefined) { exhausted = true; break; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await fetchHistory({ all: true });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /did not reach the end of pagination/.test(e.message)) {\n    console.error('Exhaustion signal unrecognized; check API end-of-pages contract');\n  } else throw e;\n}","preventionTips":["Update exhaustion detection when the API changes its end-of-pagination signal.","Retry --all runs once before surfacing the failure.","Fall back to limited fetches with incremental resumption if exhaustion cannot be confirmed."],"tags":["pagination","completeness","api","invariant-violation"],"backgroundTag":"pagination-not-exhausted","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}