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

  1. Deduplicate replies per root in fetchRepliesByRoot before building rows
  2. Deduplicate globally across all reply lists by comment id before buildCommentRows
  3. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/9b78b09b69c4051a. Report an issue: GitHub.