{"record":{"id":"7974cec0fbead5ea","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-pagination-repeated-the-same-cu","errorCode":null,"errorMessage":"Xiaoyuzhou history pagination repeated the same cursor","messagePattern":"Xiaoyuzhou history pagination repeated the same cursor","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":215,"sourceCode":"                    durationSec: episode.durationSec,\n                    progressSec: progress.progressSec,\n                    progressPct: episode.durationSec !== null && progress.progressSec !== null\n                        ? Number(((progress.progressSec / episode.durationSec) * 100).toFixed(1))\n                        : null,\n                    playedAt: progress.playedAt,\n                    pubDate: episode.pubDate,\n                    finished: episode.finished,\n                    url: `https://www.xiaoyuzhoufm.com/episode/${episode.eid}`,\n                });\n            }\n        }\n        if (!fetchAll && rows.length >= limit) break;\n        if (page.next === null) {\n            exhausted = true;\n            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}","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L197-L233","documentation":"During pagination, fetchHistory guards against a next-cursor that equals the special 'load more' sentinel or one already visited. Repeating a cursor would loop forever fetching the same page, so the library aborts with this error.","triggerScenarios":"The API returns the same next cursor twice in a row, returns a cursor equal to the current loadMoreKey, or a response shape change makes page.next stale/unchanged; also triggered when the loadMore sentinel value is echoed back as a real cursor.","commonSituations":"Upstream cursor-encoding changes (e.g. cursor now needs base64 wrapping); server bugs returning the last page's cursor again; proxies replaying cached responses; mock servers always returning a fixed next cursor.","solutions":["Log the raw page responses around the failure to see the duplicated cursor value and whether it is the sentinel.","Check for an API change in cursor format and update cursor handling/encoding accordingly.","Disable response caching/proxying so each page request hits the live API.","Report the looping cursor to the API provider if the live service is at fault."],"exampleFix":"// before\nconst next = page.next; // used as-is\n// after (API changed cursor encoding)\nconst next = typeof page.next === 'string' && !page.next.startsWith('enc:')\n  ? 'enc:' + btoa(page.next)\n  : page.next;","handlingStrategy":"retry","validationCode":"const seenCursors = new Set();\nif (page.next && seenCursors.has(page.next)) throw new Error('cursor loop detected before continuing');\nseenCursors.add(page.next);","typeGuard":null,"tryCatchPattern":"try {\n  const rows = await fetchHistory(args);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /repeated the same cursor/.test(e.message)) {\n    console.error('Upstream cursor bug or format change; disable caches and inspect cursors');\n  } else throw e;\n}","preventionTips":["Disable caching proxies/middleware for paginated API calls.","Update cursor encoding promptly when the API format changes.","Log cursors per page to diagnose loops quickly."],"tags":["pagination","infinite-loop-guard","api","cursor"],"backgroundTag":"pagination-cursor-loop","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}