jackwener/OpenCLI · error · CommandExecutionError
Zhihu answer comments contained a reply cycle at ${cursor}
Error message
Zhihu answer comments contained a reply cycle at ${cursor} What it means
resolveDepths computes each child's depth by walking reply_comment_id links up to the root. If the walk revisits a node already on the current path (active set), the parent links form a cycle, which would loop forever. The library throws naming the cursor id where the cycle was detected.
Source
Thrown at clis/zhihu/answer-comments-helpers.js:204
if (root.child_comment_count === 0) continue;
const children = await fetchChildComments(page, id, repliesLimit);
if (children.length === 0) {
throw new CommandExecutionError(`Zhihu answer root comment ${id} advertised replies but returned none`);
}
repliesByRoot.set(id, children);
}
return repliesByRoot;
}
function resolveDepths(rootId, childrenById) {
const depths = new Map();
for (const childId of childrenById.keys()) {
if (depths.has(childId)) continue;
const chain = [];
const active = new Set();
let cursor = childId;
let depth = 0;
while (cursor !== rootId && !depths.has(cursor)) {
if (active.has(cursor)) throw new CommandExecutionError(`Zhihu answer comments contained a reply cycle at ${cursor}`);
active.add(cursor);
chain.push(cursor);
const node = childrenById.get(cursor);
if (!node) throw new CommandExecutionError(`Zhihu answer comments referenced missing parent ${cursor}`);
cursor = node.descriptor.parentId;
}
if (cursor !== rootId) depth = depths.get(cursor);
for (let index = chain.length - 1; index >= 0; index -= 1) depths.set(chain[index], ++depth);
}
return depths;
}
function toRow(comment, descriptor, ranks, context) {
return {
rank: ranks.rank,
comment_rank: ranks.commentRank,
reply_rank: ranks.replyRank,
depth: ranks.depth,
id: descriptor.id,View on GitHub (pinned to 49907e53dc)
Solutions
- Dump the childrenById map for the failing thread and trace reply_comment_id links to find the cycle members.
- Check whether any middleware rewrote ids (anonymizers/proxies) and fix the rewriting so ids stay unique and acyclic.
- Refetch the thread; genuine corruption is often transient cache content.
- Patch resolveDepths to cap walk length (e.g. at child count) if you must tolerate dirty data.
Example fix
// before: unbounded walk
while (cursor !== rootId && !depths.has(cursor)) { ... }
// after: bounded walk as a defense in depth
let hops = 0;
while (cursor !== rootId && !depths.has(cursor)) {
if (hops++ > childrenById.size) throw new CommandExecutionError(`reply chain too long at ${cursor}`);
...
} Defensive patterns
Strategy: validation
Validate before calling
// detect cycles in the parent-link graph before calling the library
function hasParentCycle(children) {
for (const c of children) {
const seen = new Set([String(c.id)]);
let cur = c.reply_comment_id ? String(c.reply_comment_id) : null;
while (cur && cur !== String(c.reply_root_comment_id)) {
if (seen.has(cur)) return String(c.id);
seen.add(cur);
const node = children.find(x => String(x.id) === cur);
cur = node?.reply_comment_id ? String(node.reply_comment_id) : null;
}
}
return null;
} Try / catch
try {
const tree = await buildCommentTree(answerId);
} catch (err) {
if (String(err.message).includes('reply cycle')) {
console.error('Parent links are cyclic — payload corrupted; aborting this thread and refetching');
return buildCommentTree(answerId);
}
throw err;
} Prevention
- Never rewrite comment ids in post-processing (anonymizers must keep links consistent).
- Treat proxy/cache content as untrusted; refetch when graph invariants fail.
- Add an independent cycle check on your stored snapshots before replaying them.
When it happens
Trigger: Two or more child comments point at each other (or at themselves) via reply_comment_id, e.g. A.parent=B and B.parent=A, while neither is the rootId nor already resolved in depths.
Common situations: Corrupted or adversarially crafted API payloads, cache poisoning serving mutated rows, or manual post-processing of rows (e.g. rewriting ids for anonymization) that accidentally reused a parent id. Extremely rare with genuine Zhihu data.
Related errors
- Zhihu answer comments contained a malformed ${role} row
- Zhihu answer comment ${id} had conflicting root provenance
- Zhihu answer comments returned conflicting data for comment
- Zhihu answer comments referenced missing parent ${cursor}
- Bilibili creator comparison returned a malformed manuscript
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/83713c5348e13179.
Report an issue: GitHub.