{"record":{"id":"974bd04adcd8cc8d","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-playback-progress-omitted-requested-eid","errorCode":null,"errorMessage":"Xiaoyuzhou playback progress omitted requested eid ${episode.eid}; the history join is incomplete","messagePattern":"Xiaoyuzhou playback progress omitted requested eid (.+?); the history join is incomplete","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":139,"sourceCode":"        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, {\n            progressSec,\n            playedAt: optionalIsoTime(row.playedAt, `playedAt in row ${index + 1}`),\n        });\n    }\n    for (const episode of episodes) {\n        if (!progressById.has(episode.eid)) {\n            throw new CommandExecutionError(\n                `Xiaoyuzhou playback progress omitted requested eid ${episode.eid}; the history join is incomplete`,\n            );\n        }\n    }\n    return progressById;\n}\n\nasync function fetchHistory(args = {}) {\n    const fetchAll = args.all ?? false;\n    if (typeof fetchAll !== 'boolean') {\n        throw new ArgumentError('--all must be a boolean');\n    }\n    const limit = fetchAll ? null : positiveInteger(args.limit ?? DEFAULT_LIMIT, 'limit', MAX_LIMIT);\n    const maxPages = positiveInteger(args['max-pages'] ?? DEFAULT_MAX_PAGES, 'max-pages', HARD_MAX_PAGES);\n    let credentials = loadXiaoyuzhouCredentials();\n    const seenEpisodeIds = new Set();\n    const seenCursors = new Set();\n    const rows = [];","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L121-L157","documentation":"The progress join must be complete: every episode collected from history must have a corresponding progress row. This error means at least one requested eid never appeared in the progress response, so the library cannot return a fully populated progress map and fails fast instead of returning partial data.","triggerScenarios":"The progress endpoint returns fewer rows than the requested episodes — e.g. the API drops recently added episodes, a paginated progress fetch stops early, or eid values differ subtly (whitespace/case) between history and progress responses.","commonSituations":"Newly played episodes not yet indexed by the progress endpoint; truncated progress pages due to a pagination bug; unnoticed server-side filtering; string normalization differences on eids introduced by local preprocessing.","solutions":["Compare the missing eids against a raw progress response to see whether the API truly omitted them.","Ensure your progress fetch paginates until it covers all requested eids rather than stopping after one page.","Normalize eids (trim, lowercase) consistently before both fetches if any preprocessing is applied.","Fetch progress per-episode for missing eids, or treat missing progress as null if your handling allows it."],"exampleFix":"// before\nconst rows = await fetchProgressPage(page1Cursor);\nreturn parseProgressRows(rows, episodes);\n// after\nlet rows = [];\nlet cursor = null;\ndo {\n  const page = await fetchProgressPage(cursor);\n  rows = rows.concat(page.entries);\n  cursor = page.next;\n} while (cursor && !coversAllEids(rows, episodes));\nreturn parseProgressRows(rows, episodes);","handlingStrategy":"try-catch","validationCode":"const missing = episodes.filter(ep => !eidSet.has(ep.eid));\nif (missing.length) console.warn('eids not present in progress response:', missing.map(e => e.eid));","typeGuard":null,"tryCatchPattern":"try {\n  const progress = progressById(episodes);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /omitted requested eid/.test(e.message)) {\n    console.warn('Incomplete progress join:', e.message); // degrade gracefully\n  } else throw e;\n}","preventionTips":["Paginate progress until all requested eids are covered.","Normalize eids identically before both fetches.","Treat newly played episodes as possibly not yet present in the progress endpoint."],"tags":["api","data-integrity","pagination","incomplete-data"],"backgroundTag":"incomplete-join-result","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}