{"record":{"id":"18eed7c09da020e7","repo":"jackwener/OpenCLI","slug":"zhihu-answer-comments-referenced-missing-parent","errorCode":null,"errorMessage":"Zhihu answer comments referenced missing parent ${cursor}","messagePattern":"Zhihu answer comments referenced missing parent (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":208,"sourceCode":"        }\n        repliesByRoot.set(id, children);\n    }\n    return repliesByRoot;\n}\nfunction resolveDepths(rootId, childrenById) {\n    const depths = new Map();\n    for (const childId of childrenById.keys()) {\n        if (depths.has(childId)) continue;\n        const chain = [];\n        const active = new Set();\n        let cursor = childId;\n        let depth = 0;\n        while (cursor !== rootId && !depths.has(cursor)) {\n            if (active.has(cursor)) throw new CommandExecutionError(`Zhihu answer comments contained a reply cycle at ${cursor}`);\n            active.add(cursor);\n            chain.push(cursor);\n            const node = childrenById.get(cursor);\n            if (!node) throw new CommandExecutionError(`Zhihu answer comments referenced missing parent ${cursor}`);\n            cursor = node.descriptor.parentId;\n        }\n        if (cursor !== rootId) depth = depths.get(cursor);\n        for (let index = chain.length - 1; index >= 0; index -= 1) depths.set(chain[index], ++depth);\n    }\n    return depths;\n}\nfunction toRow(comment, descriptor, ranks, context) {\n    return {\n        rank: ranks.rank,\n        comment_rank: ranks.commentRank,\n        reply_rank: ranks.replyRank,\n        depth: ranks.depth,\n        id: descriptor.id,\n        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),","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L190-L226","documentation":"resolveDepths walks each reply's parent chain up to the root comment to compute thread depth. If the chain reaches a comment id that is not present in childrenById (i.e. the parent was never fetched or was filtered out), the graph is incomplete, so it throws. This guards against producing rows with wrong or infinite depth values.","triggerScenarios":"A reply's descriptor.parentId points to a comment that is not among the fetched replies for that root (parent deleted, hidden by Zhihu, or excluded by the --replies-limit/pagination cut).","commonSituations":"Zhihu deleted a mid-thread parent comment so children remain but the parent no longer appears in the API response; or replies were truncated by replies-limit while children of deeper nodes were still fetched; or the API returned comments out of order after an A/B change.","solutions":["Verify the reply chain: fetch with a larger --replies-limit so intermediate parents are included","Skip (or attach to root) replies whose parent is missing instead of throwing — patch resolveDepths to treat unknown parents as depth 1","Log cursor and the set of fetched ids to identify which parent id is absent, then check whether Zhihu's API omits deleted comments","Retry later — if the parent is temporarily hidden it may reappear"],"exampleFix":"// before\nconst node = childrenById.get(cursor);\nif (!node) throw new CommandExecutionError(`Zhihu answer comments referenced missing parent ${cursor}`);\n// after\nconst node = childrenById.get(cursor);\nif (!node) { depths.set(cursor, depth + 1); break; } // tolerate deleted/omitted parents","handlingStrategy":"validation","validationCode":"const fetchedIds = new Set(Object.keys(childrenById));\nfor (const node of childrenById.values()) {\n  if (node.descriptor.parentId !== rootId && !fetchedIds.has(node.descriptor.parentId)) {\n    console.warn('parent not fetched:', node.descriptor.parentId);\n  }\n}","typeGuard":"const hasParent = (node) => typeof node?.descriptor?.parentId === 'string' && (node.descriptor.parentId === rootId || childrenById.has(node.descriptor.parentId));","tryCatchPattern":"try { depths = resolveDepths(rootId, childrenById); }\ncatch (err) {\n  if (String(err.message).includes('referenced missing parent')) { /* rebuild rows skipping orphaned replies */ }\n  else throw err;\n}","preventionTips":["Fetch enough replies per root that parent chains stay complete","Log missing parent ids to detect API omissions (deleted comments)","Treat orphaned replies as depth-1 children of the root in preprocessing"],"tags":["zhihu","comments","data-integrity","graph"],"backgroundTag":"missing-parent-comment","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}