{"record":{"id":"9250956463db4bd2","repo":"jackwener/OpenCLI","slug":"commandname-malformed-comment-row-images","errorCode":null,"errorMessage":"${commandName}: malformed comment row images","messagePattern":"(.+?): malformed comment row images","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/comments.js","lineNumber":80,"sourceCode":"    const unit = short[2].toLowerCase();\n    const multiplier = unit === 'w' || unit === '万' ? 10000 : 1000;\n    return Math.round(numeric * multiplier);\n}\n\nfunction normalizeOptionalString(value, field, commandName) {\n    if (value == null)\n        return '';\n    if (typeof value !== 'string') {\n        throw new CommandExecutionError(`${commandName}: malformed comment row ${field}`);\n    }\n    return value;\n}\n\nexport function normalizeCommentImages(value, commandName) {\n    if (value == null)\n        return [];\n    if (!Array.isArray(value)) {\n        throw new CommandExecutionError(`${commandName}: malformed comment row images`);\n    }\n    const urls = [];\n    for (const raw of value) {\n        if (typeof raw !== 'string') {\n            throw new CommandExecutionError(`${commandName}: malformed comment row image URL`);\n        }\n        const trimmed = raw.trim();\n        let parsed;\n        try {\n            parsed = new URL(trimmed);\n        }\n        catch {\n            throw new CommandExecutionError(`${commandName}: malformed comment row image URL`);\n        }\n        if ((parsed.protocol !== 'https:' && parsed.protocol !== 'http:') || parsed.username || parsed.password) {\n            throw new CommandExecutionError(`${commandName}: malformed comment row image URL`);\n        }\n        const href = parsed.toString();","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/comments.js#L62-L98","documentation":"normalizeCommentImages expects the images field of a comment row to be an array (possibly null). A non-array value is treated as a malformed row and throws CommandExecutionError.","triggerScenarios":"row.images is a string, object, or number rather than an array of URL strings.","commonSituations":"Upstream payload change where images became an object map, or caller hand-assembling rows and passing a single URL string instead of an array.","solutions":["Inspect the raw comment row to confirm the images field's actual type.","Wrap a single string in an array before passing it (or add coercion in normalizeCommentImages).","Pre-validate rows with a schema/type check before normalizing.","Update the normalizer if XHS changed the images container shape."],"exampleFix":"// before\nnormalizeCommentImages(row.images, cmd);\n// after\nconst images = Array.isArray(row.images) ? row.images\n  : typeof row.images === 'string' ? [row.images] : [];\nnormalizeCommentImages(images, cmd);","handlingStrategy":"type-guard","validationCode":"if (row.images != null && !Array.isArray(row.images)) {\n  row.images = typeof row.images === 'string' ? [row.images] : [];\n}","typeGuard":"function isImageList(v) {\n  return v == null || (Array.isArray(v) && v.every(x => typeof x === 'string'));\n}","tryCatchPattern":"try {\n  images = normalizeCommentImages(row.images, cmd);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /images/.test(e.message)) {\n    images = [];\n  } else throw e;\n}","preventionTips":["Always pass arrays for image fields, never a bare string.","Normalize upstream payload shapes (object maps -> arrays) before validation.","Check XHS payload format changes when this error appears en masse.","Default to [] for images when the field is optional for your use case."],"tags":["validation","schema","scraping"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}