{"record":{"id":"1b5f8d4f53723bd6","repo":"jackwener/OpenCLI","slug":"xiaohongshu-creator-note-detail-signed-api-suff","errorCode":null,"errorMessage":"xiaohongshu creator-note-detail: signed API ${suffix} returned malformed ${key}","messagePattern":"xiaohongshu creator-note-detail: signed API (.+?) returned malformed (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/creator-note-detail.js","lineNumber":273,"sourceCode":"    if (!url)\n        return null;\n    try {\n        const parsed = new URL(String(url), 'https://creator.xiaohongshu.com');\n        return DETAIL_API_ENDPOINTS.find((endpoint) => parsed.pathname === endpoint.suffix) ?? null;\n    }\n    catch {\n        return null;\n    }\n}\nfunction findCapturedUrl(captureMap, suffix) {\n    return Object.keys(captureMap).find((url) => detailApiEndpointForUrl(url)?.suffix === suffix);\n}\nfunction isPlainObject(value) {\n    return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\nfunction assertOptionalArray(payload, key, suffix) {\n    if (key in payload && !Array.isArray(payload[key])) {\n        throw new CommandExecutionError(`xiaohongshu creator-note-detail: signed API ${suffix} returned malformed ${key}`);\n    }\n}\nfunction assertOptionalPlainObject(payload, key, suffix) {\n    if (key in payload && !isPlainObject(payload[key])) {\n        throw new CommandExecutionError(`xiaohongshu creator-note-detail: signed API ${suffix} returned malformed ${key}`);\n    }\n}\nfunction validateCapturedPayload(payload, endpoint) {\n    const suffix = endpoint.suffix;\n    if (!isPlainObject(payload)) {\n        throw new CommandExecutionError(`xiaohongshu creator-note-detail: signed API ${suffix} returned a malformed payload`);\n    }\n    if (endpoint.key === 'noteBase') {\n        assertOptionalPlainObject(payload, 'hour', suffix);\n        assertOptionalPlainObject(payload, 'day', suffix);\n    }\n    if (endpoint.key === 'audienceSource') {\n        assertOptionalArray(payload, 'source', suffix);","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/creator-note-detail.js#L255-L291","documentation":"assertOptionalArray guards optional fields in a captured signed-API payload: if a key is present it must be an array. When the creator-platform endpoint returns a field like hour/day/stats that exists but is not an array (object, string, null-shaped), the command rejects the payload as malformed.","triggerScenarios":"validateCapturedPayload calls assertOptionalArray for a key (e.g. on audienceSource or noteBase-derived endpoints) and payload[key] is present but Array.isArray fails — typically an error object or nested object returned instead of a list.","commonSituations":"Xiaohongshu creator API changed the field shape (object keyed by date instead of array); API returned an error body that still parsed as JSON; account lacks analytics permission so the endpoint returns a different structure.","solutions":["Dump the raw capture.body for the failing suffix and compare the field's actual shape against the expected array","Handle the alternative shape (e.g. convert object-of-days to an array) before validation","Update validateCapturedPayload/assertOptionalArray to accept the new API shape","Check API/account permissions — a degraded response may accompany permission or plan changes"],"exampleFix":"// before\nif (key in payload && !Array.isArray(payload[key])) {\n    throw new CommandExecutionError(`... returned malformed ${key}`);\n}\n// after\nif (key in payload && !Array.isArray(payload[key])) {\n    const v = payload[key];\n    if (v && typeof v === 'object' && !Array.isArray(v)) {\n        payload[key] = Object.values(v); // coerce object-of-values to array\n    } else {\n        throw new CommandExecutionError(`... returned malformed ${key}`);\n    }\n}","handlingStrategy":"type-guard","validationCode":"const isArr = (payload, key) => !(key in payload) || Array.isArray(payload[key]);\nif (!isArr(payload, 'hour') || !isArr(payload, 'day')) throw new Error('unexpected stats shape');","typeGuard":"const isOptionalArray = (obj, key) => !(key in obj) || Array.isArray(obj[key]);","tryCatchPattern":"try {\n  const detail = await cli.creatorNoteDetail(noteId);\n} catch (err) {\n  if (err.message.includes('returned malformed')) {\n    // log raw capture.body and degrade gracefully (skip stats)\n  } else throw err;\n}","preventionTips":["Snapshot real API responses and validate shapes in CI","Handle object-vs-array stats shapes defensively","Check account analytics permissions before querying","Watch for creator API version changes"],"tags":["validation","api-response","data-shape","signed-api"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}