{"record":{"id":"742ce0c3c18b61aa","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-returned-an-invalid-loadmorekey","errorCode":null,"errorMessage":"Xiaoyuzhou history returned an invalid loadMoreKey","messagePattern":"Xiaoyuzhou history returned an invalid loadMoreKey","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":79,"sourceCode":"    }\n    const payload = response?.data;\n    let entries;\n    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}`),","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L61-L97","documentation":"When a pagination cursor (loadMoreKey) is present, it must be a non-empty string; the library trims it and passes it back as the next request body. A cursor that is a number, object, null-like, or whitespace-only string cannot be safely forwarded, so CommandExecutionError is thrown instead of issuing a malformed continuation request.","triggerScenarios":"The history API returns loadMoreKey as a non-string (number, nested object) or an all-whitespace string while more pages remain, during `xiaoyuzhou history` or `--all` pagination.","commonSituations":"API version change alters cursor encoding (e.g. opaque object cursor instead of string token); a transparent proxy truncates the cursor; a mock fixture uses a placeholder cursor value.","solutions":["Log the raw loadMoreKey value from the failing page to see its actual type/content.","Retry the command — a transiently truncated response can produce an empty cursor string.","Update the CLI/library if the API switched to a non-string cursor format.","Fetch with a smaller --limit (or without --all) to reduce pages traversed, sidestepping the bad cursor.","Report/patch parseHistoryPage to normalize the new cursor format."],"exampleFix":"// before\n{ \"loadMoreKey\": { \"offset\": 20 } }\n// after (expected string cursor)\n{ \"loadMoreKey\": \"eyJvZmZzZXQiOjIwfQ==\" }","handlingStrategy":"validation","validationCode":"function isValidCursor(k) { return k == null || k === '' || (typeof k === 'string' && k.trim().length > 0); }\n// verify before paginating: every page.next satisfies isValidCursor","typeGuard":"function isNonEmptyString(v) { return typeof v === 'string' && v.trim().length > 0; }","tryCatchPattern":"try {\n  const rows = await runHistory();\n} catch (e) {\n  if (e.message === 'Xiaoyuzhou history returned an invalid loadMoreKey') {\n    // stop pagination and return rows collected so far\n  } else throw e;\n}","preventionTips":["Retry on transient truncation; cursors corrupted in transit usually succeed on retry","Avoid response-rewriting proxies between the CLI and api.xiaoyuzhoufm.com","Update the CLI when the API migrates to non-string cursor tokens"],"tags":["api-response","pagination","cursor","xiaoyuzhou"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}