jackwener/OpenCLI · error · CommandExecutionError
Zhihu answer comments reused id ${descriptor.id} across grap
Error message
Zhihu answer comments reused id ${descriptor.id} across graph roles What it means
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.
Source
Thrown at clis/zhihu/answer-comments-helpers.js:252
export function buildCommentRows(roots, repliesByRoot, context) {
const rows = [];
const globalIds = new Set();
for (let rootIndex = 0; rootIndex < roots.length; rootIndex += 1) {
const root = roots[rootIndex];
const rootDescriptor = describeComment(root, 'root');
if (globalIds.has(rootDescriptor.id)) {
throw new CommandExecutionError(`Zhihu answer comments reused id ${rootDescriptor.id} across graph roles`);
}
globalIds.add(rootDescriptor.id);
rows.push(toRow(root, rootDescriptor, {
rank: rows.length + 1, commentRank: rootIndex + 1, replyRank: 0, depth: 0,
}, context));
const childrenById = new Map();
for (const child of repliesByRoot.get(rootDescriptor.id) || []) {
const descriptor = describeComment(child, 'child', rootDescriptor.id);
if (globalIds.has(descriptor.id) || childrenById.has(descriptor.id)) {
throw new CommandExecutionError(`Zhihu answer comments reused id ${descriptor.id} across graph roles`);
}
globalIds.add(descriptor.id);
childrenById.set(descriptor.id, { comment: child, descriptor });
}
const depths = resolveDepths(rootDescriptor.id, childrenById);
let replyRank = 0;
for (const { comment, descriptor } of childrenById.values()) {
replyRank += 1;
rows.push(toRow(comment, descriptor, {
rank: rows.length + 1,
commentRank: rootIndex + 1,
replyRank,
depth: depths.get(descriptor.id),
}, context));
}
}
return rows;
}View on GitHub (pinned to 49907e53dc)
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
Example fix
// before
const repliesByRoot = await fetchRepliesByRoot(page, roots, repliesLimit);
return buildCommentRows(roots, repliesByRoot, { answerId, questionId });
// after
const seen = new Set();
for (const [rootId, replies] of repliesByRoot) {
repliesByRoot.set(rootId, replies.filter(r => (seen.has(r.id) ? false : (seen.add(r.id), true))));
}
return buildCommentRows(roots, repliesByRoot, { answerId, questionId }); Defensive patterns
Strategy: validation
Validate before calling
const all = roots.flatMap(r => repliesByRoot.get(r.id) ?? []);
const ids = all.map(commentId);
if (new Set(ids).size !== ids.length) console.warn('duplicate reply ids across roots'); Type guard
const repliesAreUnique = (repliesByRoot) => {
const seen = new Set();
for (const list of repliesByRoot.values()) for (const r of list) { if (seen.has(r.id)) return false; seen.add(r.id); }
return true;
}; Try / catch
try { rows = buildCommentRows(roots, repliesByRoot, ctx); }
catch (err) {
if (String(err.message).includes('reused id')) {
repliesByRoot = dedupeReplies(repliesByRoot); rows = buildCommentRows(roots, repliesByRoot, ctx);
} else throw err;
} Prevention
- 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
When it happens
Trigger: A reply appears in more than one root's reply page (comment moved/parent deleted), or fetchRepliesByRoot returned duplicates within one root's list.
Common situations: 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.
Related errors
- Zhihu answer comments referenced missing parent ${cursor}
- Zhihu answer comments reused id ${rootDescriptor.id} across
- zhihu answer-comments
- errMsg
- Zhihu ${label} returned malformed JSON: ${payload.__malforme
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/9b78b09b69c4051a.
Report an issue: GitHub.