{"record":{"id":"00763ac86a4c9788","repo":"jackwener/OpenCLI","slug":"zhihu-answer-root-comment-id-advertised-replies","errorCode":null,"errorMessage":"Zhihu answer root comment ${id} advertised replies but returned none","messagePattern":"Zhihu answer root comment (.+?) advertised replies but returned none","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/answer-comments-helpers.js","lineNumber":189,"sourceCode":"        limit,\n        label: 'answer child comments',\n        role: 'child',\n        expectedRootId: rootId,\n        normalizeNext: (next) => normalizePageUrl(next, path, { limit: String(PAGE_SIZE), offset: null }),\n    });\n}\nexport async function fetchRepliesByRoot(page, roots, repliesLimit) {\n    const repliesByRoot = new Map();\n    if (repliesLimit === 0) return repliesByRoot;\n    for (const root of roots) {\n        const id = describeComment(root, 'root').id;\n        if (!Number.isInteger(root.child_comment_count) || root.child_comment_count < 0) {\n            throw new CommandExecutionError(`Zhihu answer root comment ${id} had malformed child count`);\n        }\n        if (root.child_comment_count === 0) continue;\n        const children = await fetchChildComments(page, id, repliesLimit);\n        if (children.length === 0) {\n            throw new CommandExecutionError(`Zhihu answer root comment ${id} advertised replies but returned none`);\n        }\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);","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/answer-comments-helpers.js#L171-L207","documentation":"When a root comment advertises child_comment_count > 0, fetchRepliesByRoot requires the child-comment endpoint to actually return at least one reply. Zero replies means the advertised replies are unreachable (deleted, permission-filtered, or endpoint drift), producing an incomplete tree, so the library throws.","triggerScenarios":"root.child_comment_count >= 1 but fetchChildComments(page, id, repliesLimit) returns an empty array — the reply listing for that root yielded no rows.","commonSituations":"All replies to a root were deleted moments after the root's count was cached; Zhihu filtering replies (deleted authors, censored content) that still count toward child_comment_count; or a wrong repliesLimit of 0 interacting with stale counts. Also hit when the reply endpoint URL changed.","solutions":["Refetch the root list and its replies together so counts and listings come from the same snapshot.","Check whether the replies were deleted/hidden on zhihu.com for that root id.","Verify the child-comment endpoint path in fetchChildComments still matches Zhihu's current API.","If your policy tolerates vanished replies, relax this check to log-and-skip instead of throw."],"exampleFix":"// before\nif (children.length === 0) throw new CommandExecutionError(`... advertised replies but returned none`);\n// after: tolerate vanished replies\nif (children.length === 0) continue; // replies were removed server-side","handlingStrategy":"try-catch","validationCode":"// skip roots whose advertised replies cannot be fetched, instead of failing the batch\nasync function safeReplies(page, roots, limit) {\n  const map = new Map();\n  for (const root of roots) {\n    try {\n      const kids = await fetchChildComments(page, String(root.id), limit);\n      if (kids.length > 0) map.set(String(root.id), kids);\n    } catch { /* root advertised replies that are gone; skip */ }\n  }\n  return map;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const byRoot = await fetchRepliesByRoot(page, roots, repliesLimit);\n} catch (err) {\n  if (String(err.message).includes('advertised replies but returned none')) {\n    console.warn('Replies vanished between count and fetch; continuing with roots only');\n    return fetchRootComments(page, answerId, order, limit);\n  }\n  throw err;\n}","preventionTips":["Treat child_comment_count as a hint, not a guarantee — replies may be deleted at any time.","Fetch root list and reply pages back-to-back to shrink the consistency window.","Design output to tolerate missing reply subtrees."],"tags":["api","data-consistency","zhihu","deleted-content"],"backgroundTag":"inconsistent-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}