{"record":{"id":"042201d0567a3c01","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-playback-progress-returned-unrequested","errorCode":null,"errorMessage":"Xiaoyuzhou playback progress returned unrequested eid ${eid}","messagePattern":"Xiaoyuzhou playback progress returned unrequested eid (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":119,"sourceCode":"        pubDate: optionalIsoTime(episode.pubDate, `pubDate in row ${rowNumber}`, { required: true }),\n        finished: episode.isFinished,\n    };\n}\n\nfunction parseProgressRows(response, episodes) {\n    if (!Array.isArray(response?.data)) {\n        throw new CommandExecutionError('Xiaoyuzhou playback progress returned an unexpected response shape');\n    }\n    const requested = new Map(episodes.map((episode) => [episode.eid, episode]));\n    const progressById = new Map();\n    for (const [index, row] of response.data.entries()) {\n        if (!isRecord(row)) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress row ${index + 1} is malformed`);\n        }\n        const eid = requiredId(row.eid, `progress eid in row ${index + 1}`);\n        const episode = requested.get(eid);\n        if (!episode) {\n            throw new CommandExecutionError(`Xiaoyuzhou playback progress returned unrequested eid ${eid}`);\n        }\n        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) {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L101-L137","documentation":"parseProgressRows builds a Map of the eids it explicitly requested from the history page and rejects any progress row whose eid was not requested. This guards against the server returning unrelated or wrong-scope progress records, which would silently corrupt the history/progress join.","triggerScenarios":"During `xiaoyuzhou history`, the progress endpoint returns a row whose eid is absent from the eids posted in the request body — e.g. the server ignores the eids filter, returns all account progress, or case-mismatched 24-hex ids fail the exact Map lookup.","commonSituations":"API regression where the eids filter is ignored; eid case differences (uppercase vs lowercase hex) after requiredId lowercased history ids; shared-account/sync quirks; mock fixtures returning canned full-list responses.","solutions":["Verify the eids sent in the request body match the returned rows — capture both sides of the call.","Retry; a server-side filter regression may be transient or already fixed.","Update the CLI/library in case id normalization (e.g. casing) changed and caused the mismatch.","Check for server-side API changes where the progress endpoint stopped honoring the eids filter, and report it.","As a workaround, use a lower --limit so fewer eids are in flight, reducing the chance of a mismatched row."],"exampleFix":"// before (server ignores filter, returns everything)\n{ \"data\": [ { \"eid\": \"ffff...\", \"pid\": \"...\" } ] }  // ffff... was not requested\n// after\n{ \"data\": [ { \"eid\": \"aabb...\", \"pid\": \"...\", \"progress\": 120 } ] }  // only requested eids","handlingStrategy":"validation","validationCode":"function requestedOnly(progressRows, eids) { const set = new Set(eids.map(e => String(e).toLowerCase())); return progressRows.every(r => set.has(String(r?.eid ?? '').toLowerCase())); }","typeGuard":"function eidIsRequested(row, requestedEids) { return typeof row?.eid === 'string' && requestedEids.map(e => e.toLowerCase()).includes(row.eid.toLowerCase()); }","tryCatchPattern":"try {\n  const rows = await runHistory();\n} catch (e) {\n  if (/returned unrequested eid/.test(e.message)) {\n    // likely server ignoring the eids filter — retry, then report the API regression\n  } else throw e;\n}","preventionTips":["Report/pin API versions where the progress endpoint's eids filter regressed","Keep id casing consistent; the CLI lowercases ids, so ensure fixtures do too","Capture both the request eids and response rows when debugging join mismatches"],"tags":["api-response","contract-violation","pagination","xiaoyuzhou"],"backgroundTag":"api-returned-unrequested-data","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}