{"record":{"id":"9b78b09b69c4051a","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comments-reused-id-descriptor-id-a","errorCode":null,"errorMessage":"Zhihu answer comments reused id ${descriptor.id} across graph roles","messagePattern":"Zhihu answer comments reused id (.+?) across graph roles","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":252,"sourceCode":"export function buildCommentRows(roots, repliesByRoot, context) {\n    const rows = [];\n    const globalIds = new Set();\n    for (let rootIndex = 0; rootIndex < roots.length; rootIndex += 1) {\n        const root = roots[rootIndex];\n        const rootDescriptor = describeComment(root, 'root');\n        if (globalIds.has(rootDescriptor.id)) {\n            throw new CommandExecutionError(`Zhihu answer comments reused id ${rootDescriptor.id} across graph roles`);\n        }\n        globalIds.add(rootDescriptor.id);\n        rows.push(toRow(root, rootDescriptor, {\n            rank: rows.length + 1, commentRank: rootIndex + 1, replyRank: 0, depth: 0,\n        }, context));\n\n        const childrenById = new Map();\n        for (const child of repliesByRoot.get(rootDescriptor.id) || []) {\n            const descriptor = describeComment(child, 'child', rootDescriptor.id);\n            if (globalIds.has(descriptor.id) || childrenById.has(descriptor.id)) {\n                throw new CommandExecutionError(`Zhihu answer comments reused id ${descriptor.id} across graph roles`);\n            }\n            globalIds.add(descriptor.id);\n            childrenById.set(descriptor.id, { comment: child, descriptor });\n        }\n        const depths = resolveDepths(rootDescriptor.id, childrenById);\n        let replyRank = 0;\n        for (const { comment, descriptor } of childrenById.values()) {\n            replyRank += 1;\n            rows.push(toRow(comment, descriptor, {\n                rank: rows.length + 1,\n                commentRank: rootIndex + 1,\n                replyRank,\n                depth: depths.get(descriptor.id),\n            }, context));\n        }\n    }\n    return rows;\n}","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L234-L270","documentation":"While inserting replies for a root, buildCommentRows enforces id uniqueness both globally (across all roots) and within the current root's childrenById. If a reply id already exists anywhere in the graph, the same comment would be emitted twice with different roles, so it throws. This typically means the API returned the same reply under two roots or twice in one reply list.","triggerScenarios":"A reply appears in more than one root's reply page (comment moved/parent deleted), or fetchRepliesByRoot returned duplicates within one root's list.","commonSituations":"Zhihu re-parenting replies after parent deletion so a reply shows under multiple threads; duplicated API responses from concurrent fetches; overlapping pages in replies pagination.","solutions":["Deduplicate replies per root in fetchRepliesByRoot before building rows","Deduplicate globally across all reply lists by comment id before buildCommentRows","Re-run with --replies-limit 0 to confirm the duplicate comes from reply fetching, not the roots"],"exampleFix":"// before\nconst repliesByRoot = await fetchRepliesByRoot(page, roots, repliesLimit);\nreturn buildCommentRows(roots, repliesByRoot, { answerId, questionId });\n// after\nconst seen = new Set();\nfor (const [rootId, replies] of repliesByRoot) {\n  repliesByRoot.set(rootId, replies.filter(r => (seen.has(r.id) ? false : (seen.add(r.id), true))));\n}\nreturn buildCommentRows(roots, repliesByRoot, { answerId, questionId });","handlingStrategy":"validation","validationCode":"const all = roots.flatMap(r => repliesByRoot.get(r.id) ?? []);\nconst ids = all.map(commentId);\nif (new Set(ids).size !== ids.length) console.warn('duplicate reply ids across roots');","typeGuard":"const repliesAreUnique = (repliesByRoot) => {\n  const seen = new Set();\n  for (const list of repliesByRoot.values()) for (const r of list) { if (seen.has(r.id)) return false; seen.add(r.id); }\n  return true;\n};","tryCatchPattern":"try { rows = buildCommentRows(roots, repliesByRoot, ctx); }\ncatch (err) {\n  if (String(err.message).includes('reused id')) {\n    repliesByRoot = dedupeReplies(repliesByRoot); rows = buildCommentRows(roots, repliesByRoot, ctx);\n  } else throw err;\n}","preventionTips":["Dedupe replies per root and globally before buildCommentRows","Be aware deleted parents can cause replies to surface under multiple roots","Capture raw API responses when duplicates occur to confirm server-side origin"],"tags":["zhihu","comments","duplicate-id","graph"],"backgroundTag":"duplicate-comment-id","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}