{"record":{"id":"5b87f5ae791284a3","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comments-contained-a-malformed-role","errorCode":null,"errorMessage":"Zhihu answer comments contained a malformed ${role} row","messagePattern":"Zhihu answer comments contained a malformed (.+?) row","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":91,"sourceCode":"        }\n        if (status === 401 || status === 403 || code === '40353' || payload.__needLogin) {\n            throw new AuthRequiredError('www.zhihu.com', `Failed to fetch Zhihu ${label}`);\n        }\n        if (status === 404 && notFoundDetail) throw new EmptyResultError('zhihu answer-comments', notFoundDetail);\n        if (status >= 400) throw new CommandExecutionError(`Zhihu ${label} request failed (HTTP ${status})`);\n        throw new CommandExecutionError(`Zhihu ${label} returned an error payload: ${payload.__errorMessage || code}`);\n    }\n    if (!Array.isArray(payload.data) || !payload.paging || typeof payload.paging !== 'object') {\n        throw new CommandExecutionError(`Zhihu ${label} returned a malformed payload`);\n    }\n    if (typeof payload.paging.is_end !== 'boolean') {\n        throw new CommandExecutionError(`Zhihu ${label} returned malformed paging state`);\n    }\n    return payload;\n}\nfunction describeComment(comment, role, expectedRootId = '') {\n    if (!comment || typeof comment !== 'object' || Array.isArray(comment)) {\n        throw new CommandExecutionError(`Zhihu answer comments contained a malformed ${role} row`);\n    }\n    const id = commentId(comment.id);\n    if (!id) throw new CommandExecutionError(`Zhihu answer comments contained a ${role} row without a stable id`);\n    const rootId = role === 'root' ? id : commentId(comment.reply_root_comment_id);\n    const parentId = role === 'root' ? '' : commentId(comment.reply_comment_id);\n    if (role === 'child' && rootId !== expectedRootId) {\n        throw new CommandExecutionError(`Zhihu answer comment ${id} had conflicting root provenance`);\n    }\n    if (role === 'child' && !parentId) {\n        throw new CommandExecutionError(`Zhihu answer comment ${id} did not identify its immediate parent`);\n    }\n    const signature = JSON.stringify({\n        role,\n        rootId,\n        parentId,\n        author: memberName(comment.author),\n        replyTo: role === 'child' ? memberName(comment.reply_to_author) : '',\n        content: stripHtml(comment.content || ''),","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L73-L109","documentation":"CommandExecutionError thrown by describeComment when a comment row (root or child, per the role argument) is null, not an object, or an Array. The library validates each row from payload.data before extracting its id and reply relationships, so downstream code can trust the descriptor shape.","triggerScenarios":"An entry in payload.data (or a nested child in reply list) is null, a primitive, or an array — e.g. Zhihu inserting null placeholders for deleted comments, or a schema change altering row structure.","commonSituations":"Deleted/removed comments serialized as null in the data array; API returning flattened rows after a schema change; children embedded differently than expected.","solutions":["Filter out non-object rows before processing: data.filter(c => c && typeof c === 'object' && !Array.isArray(c))","Handle deleted comments (null rows) by skipping them rather than failing the whole fetch","Log the offending row with -v to check for API schema changes","Update describeComment/parsing if Zhihu changed the row shape"],"exampleFix":"// before\npayload.data.forEach(c => describeComment(c, 'root')); // throws on null placeholder rows\n// after\npayload.data.filter(c => c && typeof c === 'object' && !Array.isArray(c))\n  .forEach(c => describeComment(c, 'root'));","handlingStrategy":"type-guard","validationCode":"const rows = (payload.data || []).filter(c => c && typeof c === 'object' && !Array.isArray(c));","typeGuard":"function isCommentRow(c){ return !!c && typeof c === 'object' && !Array.isArray(c) && typeof c.id !== 'undefined'; }","tryCatchPattern":"try { return describeComment(row, role, rootId); } catch (e) { if (/malformed .* row/.test(e.message)) { log(`skipping bad row: ${JSON.stringify(row)}`); return null; } throw e; }","preventionTips":["Filter null/placeholder rows before describing","Skip-and-log bad rows in batch processing instead of failing the run","Assert row schema in tests against a captured payload","Check for API schema changes when many rows suddenly fail validation"],"tags":["zhihu","schema-validation","data-integrity","null-row"],"backgroundTag":"unexpected-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}