{"record":{"id":"4978472933be06a9","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comments-contained-a-role-row-with","errorCode":null,"errorMessage":"Zhihu answer comments contained a ${role} row without a stable id","messagePattern":"Zhihu answer comments contained a (.+?) row without a stable id","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":94,"sourceCode":"        }\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 || ''),\n    });\n    return { id, rootId, parentId, signature };\n}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L76-L112","documentation":"describeComment validates each comment row fetched from Zhihu's answer comments API. A row that is a valid object but whose comment.id cannot be normalized to a non-empty stable identifier (via commentId) is rejected. The library throws because every subsequent operation (dedup, tree building, provenance checks) keys off this id; a comment without one cannot be processed safely.","triggerScenarios":"Zhihu's API returned a data row where comment.id is null, undefined, empty string, or a value commentId() normalizes to falsy (e.g. whitespace-only or non-numeric id) while the row is still an object.","commonSituations":"Zhihu schema drift (field renamed from id to id_string or similar), deleted/soft-removed comments appearing in a page payload, or proxied/cached responses with rows stripped of their id. Developers also hit it when mocking the API with fixtures missing the id field.","solutions":["Inspect the offending row in the payload and confirm the comment.id field exists and normalizes to a non-empty value.","Update this library to a version matching Zhihu's current comment_v5 API schema if the field name changed.","Filter out rows with missing ids at the network/mock layer before they reach fetchPages, if you own the fetching boundary.","Retry the fetch; if the API is returning a transient malformed page, a fresh request may contain valid rows."],"exampleFix":"// before: mock fixture row without id\nconst rows = [{ author: { member: { name: 'alice' } }, content: 'hi' }];\n// after\nconst rows = [{ id: '14839201', author: { member: { name: 'alice' } }, content: 'hi' }];","handlingStrategy":"validation","validationCode":"// validate fixtures/responses before calling the API-driven code\nfunction assertCommentRows(rows) {\n  for (const r of rows) {\n    if (r && typeof r === 'object' && !Array.isArray(r) && !String(r.id ?? '').trim()) {\n      throw new Error(`fixture row missing comment.id: ${JSON.stringify(r).slice(0, 80)}`);\n    }\n  }\n}","typeGuard":"function hasStableId(row) {\n  return !!row && typeof row === 'object' && !Array.isArray(row) &&\n    typeof row.id !== 'undefined' && row.id !== null && String(row.id).trim() !== '';\n}","tryCatchPattern":"try {\n  const comments = await fetchAnswerComments(page, answerId);\n} catch (err) {\n  if (String(err.message).includes('without a stable id')) {\n    console.warn('Skipping page with malformed comment row (missing id)');\n    return [];\n  }\n  throw err;\n}","preventionTips":["Keep API mocks regenerated from live captures so the id field is always present.","Validate third-party payloads with a schema (zod/ajv) at the network boundary.","Pin the library version known to match the current Zhihu comment_v5 schema and check for updates when the API drifts."],"tags":["api","data-validation","zhihu","schema"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}