{"record":{"id":"3d74f00e97051d20","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-returned-an-unexpected-response","errorCode":null,"errorMessage":"Xiaoyuzhou history returned an unexpected response shape","messagePattern":"Xiaoyuzhou history returned an unexpected response shape","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":60,"sourceCode":"        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}; expected seconds`);\n    }\n    return value;\n}\n\nfunction optionalIsoTime(value, label, { required = false } = {}) {\n    if (value === null) {\n        if (required) throw new CommandExecutionError(`Xiaoyuzhou history returned a missing ${label}`);\n        return null;\n    }\n    if (typeof value !== 'string' || !value.includes('T') || !Number.isFinite(Date.parse(value))) {\n        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}`);\n    }\n    return new Date(value).toISOString();\n}\n\nfunction parseHistoryPage(response) {\n    if (!isRecord(response) || !isRecord(response.raw) || response.raw.data !== response.data) {\n        throw new CommandExecutionError('Xiaoyuzhou history returned an unexpected response shape');\n    }\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()) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L42-L78","documentation":"parseHistoryPage first checks that the response object has both `data` and `raw` as records and that raw.data is identical to data — i.e. the envelope the auth/transport layer produced is intact. If the response is null, not an object, or the envelope is inconsistent, the history command cannot be trusted and throws CommandExecutionError.","triggerScenarios":"Calling the /v1/episode-played/list-history endpoint and getting a null/non-object response, or a response where response.raw.data !== response.data (transport wrapper inconsistent), typically from an auth error body, an HTML error page, or a proxy response that bypasses the wrapper.","commonSituations":"Expired credentials cause the API to return a login-redirect HTML body; a corporate proxy injects its own JSON error; using a stubbed requestXiaoyuzhouJson in tests that doesn't mirror raw/data; API version change replaces the JSON body entirely.","solutions":["Re-authenticate: refresh or reload Xiaoyuzhou credentials (loadXiaoyuzhouCredentials) and retry.","Check whether the API/domain is reachable and not being intercepted (VPN, proxy, captive portal) and retry from a plain network.","Inspect the actual HTTP body returned by /v1/episode-played/list-history to see what shape came back.","Update the CLI/library in case the transport envelope contract (raw.data === data) changed.","If using mocks or an old version, align the mock/upgrade so requestXiaoyuzhouJson returns { data, raw, credentials }."],"exampleFix":"// before (stub missing raw envelope)\nreturn { data: json, credentials };\n// after\nreturn { data: json, raw: { data: json, ...rest }, credentials };","handlingStrategy":"try-catch","validationCode":"function hasHistoryEnvelope(res) { return res !== null && typeof res === 'object' && !Array.isArray(res) && res.raw !== null && typeof res.raw === 'object' && res.raw.data === res.data; }","typeGuard":"function isHistoryEnvelope(v) { return typeof v === 'object' && v !== null && 'data' in v && 'raw' in v && typeof v.raw === 'object' && v.raw !== null; }","tryCatchPattern":"try {\n  const rows = await runHistory();\n} catch (e) {\n  if (e.message === 'Xiaoyuzhou history returned an unexpected response shape') {\n    // refresh credentials / check network proxy, then retry once\n  } else throw e;\n}","preventionTips":["Keep Xiaoyuzhou credentials fresh; re-login before long history fetches","Disable or audit proxies/VPN/captive portals that can inject non-API bodies","Keep requestXiaoyuzhouJson mocks in sync with the { data, raw, credentials } envelope"],"tags":["api-response","auth","network","xiaoyuzhou"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}