{"record":{"id":"a89c3875a91ccee8","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comment-id-did-not-identify-its-im","errorCode":null,"errorMessage":"Zhihu answer comment ${id} did not identify its immediate parent","messagePattern":"Zhihu answer comment (.+?) did not identify its immediate parent","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":101,"sourceCode":"    }\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}\nfunction addPageComments(byId, comments, role, expectedRootId) {\n    for (const comment of comments) {\n        const descriptor = describeComment(comment, role, expectedRootId);\n        const previous = byId.get(descriptor.id);\n        if (!previous) {\n            byId.set(descriptor.id, { comment, descriptor });\n            continue;","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L83-L119","documentation":"A child comment must declare its immediate parent via reply_comment_id. describeComment throws when the normalized parentId is falsy for a role='child' row, because the library builds reply depth chains (resolveDepths) by walking parentId links; a child without a parent breaks tree reconstruction.","triggerScenarios":"A row fetched as a reply has reply_comment_id missing, null, or normalizing to empty, while reply_root_comment_id is present (so it passed the root provenance check).","commonSituations":"Zhihu returning a reply row whose direct parent comment was deleted (reply_comment_id dangling to a removed id) or schema drift where the field is now named differently. Also hit with hand-made fixtures omitting reply_comment_id.","solutions":["Check the raw row: does reply_comment_id exist and normalize? If absent, treat the API payload as inconsistent and refetch.","If the parent comment was deleted, skip that subtree rather than expecting the library to attach it.","Fix fixtures/mocks to include reply_comment_id on every child row.","Update the library if Zhihu renamed the parent-reference field."],"exampleFix":"// before\n{ id: 'c2', reply_root_comment_id: 'r1' }\n// after\n{ id: 'c2', reply_root_comment_id: 'r1', reply_comment_id: 'r1' }","handlingStrategy":"type-guard","validationCode":"// drop child rows lacking a parent link before tree building\nfunction validChildRows(rows) {\n  return rows.filter(r => r.reply_comment_id != null && String(r.reply_comment_id) !== '');\n}","typeGuard":"function declaresParent(row) {\n  return row != null && typeof row === 'object' &&\n    row.reply_comment_id !== undefined && row.reply_comment_id !== null &&\n    String(row.reply_comment_id).trim() !== '';\n}","tryCatchPattern":"try {\n  const tree = await buildCommentTree(answerId);\n} catch (err) {\n  if (String(err.message).includes('did not identify its immediate parent')) {\n    console.warn('A reply lost its parent (likely deleted); flattening it under the root');\n    return buildCommentTree(answerId, { orphanPolicy: 'attach-to-root' });\n  }\n  throw err;\n}","preventionTips":["Expect deleted parents: design your pipeline to tolerate orphaned replies.","Include reply_comment_id in all test fixtures.","When snapshotting, fetch root list and reply pages in the same run to avoid dangling links."],"tags":["api","data-validation","zhihu","tree-structure"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}