{"record":{"id":"7196888d16b0090d","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comment-id-had-conflicting-root-pr","errorCode":null,"errorMessage":"Zhihu answer comment ${id} had conflicting root provenance","messagePattern":"Zhihu answer comment (.+?) had conflicting root provenance","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":98,"sourceCode":"    }\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}\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);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L80-L116","documentation":"For a child (reply) comment, describeComment normalizes reply_root_comment_id and requires it to equal the expectedRootId of the root comment thread being fetched. A mismatch means the API returned a reply whose declared root does not match the thread it appeared under. The library throws to avoid attaching a reply to the wrong root in the reconstructed tree.","triggerScenarios":"fetchChildComments requests replies for root X but the payload contains rows whose reply_root_comment_id normalizes to a different id than X. This happens when describeComment(child, 'child', expectedRootId) sees reply_root_comment_id null/absent/different.","commonSituations":"Zhihu re-parenting a reply (moved to another thread), caching layers serving stale mixed pages, or a bug in the caller passing the wrong expectedRootId when manually invoking fetchPages/fetchChildComments.","solutions":["Log the child's reply_root_comment_id and the expectedRootId to see whether the API re-parented the reply or the expected id is wrong.","Refetch the child-comment page; if Zhihu moved the reply, fresh data should be consistent.","If intentionally fetching replies of multiple roots in one pass, call fetchPages per root with the correct expectedRootId instead of merging pages.","Upgrade the library if Zhihu changed reply provenance fields (e.g. reply_root_comment_id renamed)."],"exampleFix":"// before: fetching children of root '111' but page contains reply rooted at '222'\nfetchChildComments(page, '111', limit); // throws for the '222' row\n// after: fetch per root, or pre-filter\nconst filtered = rows.filter(r => commentId(r.reply_root_comment_id) === '111');","handlingStrategy":"validation","validationCode":"// ensure every child row's declared root matches the thread you are fetching\nfunction filterChildrenForRoot(rows, expectedRootId) {\n  return rows.filter(r => String(r.reply_root_comment_id ?? '') === String(expectedRootId));\n}","typeGuard":"function belongsToRoot(child, rootId) {\n  return typeof child?.reply_root_comment_id === 'string' &&\n    child.reply_root_comment_id === String(rootId);\n}","tryCatchPattern":"try {\n  const replies = await fetchChildComments(page, rootId, limit);\n} catch (err) {\n  if (String(err.message).includes('conflicting root provenance')) {\n    console.warn(`Root ${rootId} returned replies parented elsewhere; refetching`);\n    return fetchChildComments(page, rootId, limit); // single retry for re-parented data\n  }\n  throw err;\n}","preventionTips":["Fetch replies one root at a time and never merge pages of different roots manually.","Record reply_root_comment_id in your stored snapshots so re-parenting is detectable later.","Avoid stale caches: use cache-control bypass for comment pages."],"tags":["api","data-integrity","zhihu","provenance"],"backgroundTag":"inconsistent-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}