{"record":{"id":"842633bc56a379e7","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comments-reused-id-rootdescriptor-i","errorCode":null,"errorMessage":"Zhihu answer comments reused id ${rootDescriptor.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":241,"sourceCode":"        parent_id: descriptor.parentId,\n        author: memberName(comment.author) || 'anonymous',\n        reply_to: descriptor.parentId ? memberName(comment.reply_to_author) : '',\n        likes: normalizeCount(comment.like_count ?? comment.vote_count),\n        created_at: normalizeUnixSeconds(comment.created_time),\n        url: context.questionId\n            ? `https://www.zhihu.com/question/${context.questionId}/answer/${context.answerId}#comment-${descriptor.id}`\n            : typeof comment.url === 'string' ? comment.url : '',\n        content: stripHtml(comment.content || ''),\n    };\n}\nexport 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()) {","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L223-L259","documentation":"buildCommentRows builds a strict tree: each comment id must appear exactly once across all root graphs (roots and replies). Before inserting a root comment it checks globalIds; a duplicate means the same comment id was returned as a root twice (or already seen as a reply), which would corrupt rank/reply assignment. The library throws to fail fast rather than emit duplicated rows.","triggerScenarios":"fetchRootComments returned the same root comment id twice (overlapping pagination pages), or a comment already classified as a reply also appears in the root list.","commonSituations":"Zhihu API pagination shifting while new comments arrive (score-ordered pages overlap); API returning duplicated payloads after retry; version drift in the comments endpoint.","solutions":["Deduplicate roots by id before calling buildCommentRows: const seen=new Set(); roots=roots.filter(r=>!seen.has(r.id)&&seen.add(r.id))","Re-fetch with stable ordering (--order latest) or a lower --limit to reduce page-shift overlap","Check for API retries duplicating the root list in fetchRootComments"],"exampleFix":"// before\nconst rows = buildCommentRows(roots, repliesByRoot, ctx);\n// after\nconst seen = new Set();\nconst uniqueRoots = roots.filter(r => (seen.has(r.id) ? false : (seen.add(r.id), true)));\nconst rows = buildCommentRows(uniqueRoots, repliesByRoot, ctx);","handlingStrategy":"validation","validationCode":"const ids = roots.map(r => commentId(r));\nif (new Set(ids).size !== ids.length) throw new Error('duplicate root ids before buildCommentRows');","typeGuard":"const isUniqueRoots = (roots) => new Set(roots.map(r => r.id)).size === roots.length;","tryCatchPattern":"try { rows = buildCommentRows(roots, repliesByRoot, ctx); }\ncatch (err) {\n  if (String(err.message).includes('reused id') && String(err.message).includes('graph roles')) {\n    roots = dedupeById(roots); rows = buildCommentRows(roots, repliesByRoot, ctx);\n  } else throw err;\n}","preventionTips":["Always deduplicate root lists by id before building rows","Use stable ordering to reduce pagination overlap duplicates","Keep one source of truth for comment ids across roots/replies"],"tags":["zhihu","comments","duplicate-id","pagination"],"backgroundTag":"duplicate-comment-id","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}