{"record":{"id":"49d3d093e8bd0e3b","repo":"jackwener/OpenCLI","slug":"parse-error-49d3d0","errorCode":"PARSE_ERROR","errorMessage":"PARSE_ERROR","messagePattern":"PARSE_ERROR","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/podcast-episodes.js","lineNumber":31,"sourceCode":"    args: [\n        { name: 'id', positional: true, required: true, help: 'Podcast ID (from xiaoyuzhoufm.com URL)' },\n        { name: 'limit', type: 'int', default: 20, help: 'Max episodes to show' },\n    ],\n    columns: ['eid', 'title', 'duration', 'plays', 'date'],\n    func: async (args) => {\n        const requestedLimit = Number(args.limit);\n        if (!Number.isInteger(requestedLimit) || requestedLimit < 1) {\n            throw new CliError('INVALID_ARGUMENT', 'limit must be a positive integer', 'Example: --limit 5');\n        }\n        const credentials = loadXiaoyuzhouCredentials();\n        const response = await requestXiaoyuzhouJson('/v1/episode/list', {\n            method: 'POST',\n            body: { pid: args.id, order: 'desc', limit: requestedLimit },\n            credentials,\n        });\n        const episodes = response.data ?? [];\n        if (!Array.isArray(episodes)) {\n            throw new CliError('PARSE_ERROR', 'Unexpected API response format', 'Expected an array of episodes');\n        }\n        return episodes.map((ep) => ({\n            eid: ep.eid,\n            title: ep.title,\n            duration: formatDuration(ep.duration),\n            plays: ep.playCount,\n            date: formatDate(ep.pubDate),\n        }));\n    },\n});\n","sourceCodeStart":13,"sourceCodeEnd":42,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/podcast-episodes.js#L13-L42","documentation":"After calling POST /v1/episode/list, the CLI expects `response.data` to be an array of episodes. If the API returns an object, null, or a wrapped envelope instead, a CliError with code PARSE_ERROR is thrown so the user knows the response shape changed rather than silently emitting garbage rows.","triggerScenarios":"The xiaoyuzhou API returns a non-array `data` field for /v1/episode/list — e.g. an error envelope `{error: ...}`, `{data: {items: []}}` shape change, or an HTML/JSON error page parsed into an object.","commonSituations":"API version drift after a xiaoyuzhou backend update, hitting a rate-limit or auth-failure payload that isn't the expected list, or a proxy/captive portal returning HTML that gets parsed as a JSON object.","solutions":["Re-run the command; transient error envelopes can cause this","Verify credentials are valid (`xq`-style auth failures can change the payload shape)","Check for xiaoyuzhou API/CLI version mismatch and update the CLI","Inspect the raw response (e.g. via verbose logging) to confirm the new schema"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"const res = await requestXiaoyuzhouJson('/v1/episode/list', {method:'POST', body:{pid, order:'desc', limit}});\nif (!Array.isArray(res?.data)) throw new Error('Unexpected episode list shape');","typeGuard":"const isEpisodeArray = (d) => Array.isArray(d) && d.every(e => e && typeof e.eid === 'string');","tryCatchPattern":"try { const eps = await fetchEpisodes(pid, limit); } catch (e) { if (String(e).includes('PARSE_ERROR')) { logRawResponseForDebugging(); } throw e; }","preventionTips":["Assert response.data is an array before mapping","Pin and monitor the CLI/API versions together","Log raw payloads on failure to detect schema drift early","Handle wrapped envelopes like {items: []} defensively"],"tags":["cli","api-response","schema-mismatch"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}