{"record":{"id":"a5f2ea232ec67ce0","repo":"jackwener/OpenCLI","slug":"xiaohongshu-collection-api-returned-malformed-note","errorCode":null,"errorMessage":"xiaohongshu collection API returned malformed notes","messagePattern":"xiaohongshu collection API returned malformed notes","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/collection-helpers.js","lineNumber":99,"sourceCode":"        url,\n    };\n}\n\nexport function extractNotesFromResponses(requests, fallbackUserId) {\n    const rows = [];\n    const seen = new Set();\n    for (const req of requests ?? []) {\n        const payload = unwrapBrowserResult(req);\n        if (!isObject(payload)) {\n            throw new CommandExecutionError('xiaohongshu collection API returned a malformed payload');\n        }\n        const data = payload.data;\n        if (!isObject(data)) {\n            throw new CommandExecutionError('xiaohongshu collection API returned malformed data');\n        }\n        const notes = data.notes ?? data.note_list;\n        if (!Array.isArray(notes))\n            throw new CommandExecutionError('xiaohongshu collection API returned malformed notes');\n        for (const entry of notes) {\n            const row = mapCollectionNote(entry, { fallbackUserId });\n            if (!row?.id || !row.url.includes('xsec_token=')) {\n                throw new CommandExecutionError('xiaohongshu collection API returned a note without stable id/xsec token');\n            }\n            if (seen.has(row.id))\n                continue;\n            seen.add(row.id);\n            rows.push(row);\n        }\n    }\n    return rows;\n}\n\nexport const EXTRACT_COLLECTION_DOM_JS = `\n  (() => {\n    const normalizeUrl = (href) => {\n      if (!href) return '';","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/collection-helpers.js#L81-L117","documentation":"After locating the data object, extractNotesFromResponses requires the notes array at data.notes or data.note_list. If neither key holds an array, it throws CommandExecutionError. This catches both missing keys and xiaohongshu switching the note-list field name.","triggerScenarios":"data exists but has neither notes nor note_list arrays — e.g. an empty envelope {data:{}}, or the API renamed the field; also a scalar/null at those keys.","commonSituations":"First page fetched before any notes load; user with zero notes returning an empty object instead of an empty array; upstream API version change (notes vs note_list) not covered by either key.","solutions":["Confirm the account/collection actually returns notes by checking the endpoint in a browser devtools session","Capture and log Object.keys(payload.data) to find the new field name","Update mapping to support the new key in a local fork or newer library version","Retry later if the payload was transiently incomplete"],"exampleFix":"// before\nconst notes = data.notes ?? data.note_list;\n// after (defensive, before calling extractNotesFromResponses)\nconst data = payload?.data ?? {};\nconst notes = data.notes ?? data.note_list ?? (data.items?.notes);\nif (!Array.isArray(notes)) throw new Error('unexpected keys: ' + Object.keys(data));","handlingStrategy":"type-guard","validationCode":"const notesOf = (payload) => payload?.data ? (payload.data.notes ?? payload.data.note_list) : undefined;\nconst hasNotesArray = (p) => Array.isArray(notesOf(p));","typeGuard":"const hasNotesArray = (p) => Array.isArray(p?.data?.notes ?? p?.data?.note_list);","tryCatchPattern":"try { rows = extractNotesFromResponses(reqs, userId); } catch (e) { if (String(e.message).includes('malformed notes')) { console.warn('data keys:', Object.keys(reqs[0]?.data ?? {})); rows = []; } else throw e; }","preventionTips":["Support both notes and note_list key spellings in any custom preprocessing","Treat an empty data object as 'no notes yet' rather than a hard failure","Check the endpoint in devtools after suspected API version changes","Only pass fully-loaded captures to the extractor"],"tags":["scraping","malformed-response","api-schema","command-execution-error"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}