jackwener/OpenCLI · error · CommandExecutionError
Zhihu answer comment ${id} had conflicting root provenance
Error message
Zhihu answer comment ${id} had conflicting root provenance What it means
For a child (reply) comment, describeComment normalizes reply_root_comment_id and requires it to equal the expectedRootId of the root comment thread being fetched. A mismatch means the API returned a reply whose declared root does not match the thread it appeared under. The library throws to avoid attaching a reply to the wrong root in the reconstructed tree.
Source
Thrown at clis/zhihu/answer-comments-helpers.js:98
}
if (!Array.isArray(payload.data) || !payload.paging || typeof payload.paging !== 'object') {
throw new CommandExecutionError(`Zhihu ${label} returned a malformed payload`);
}
if (typeof payload.paging.is_end !== 'boolean') {
throw new CommandExecutionError(`Zhihu ${label} returned malformed paging state`);
}
return payload;
}
function describeComment(comment, role, expectedRootId = '') {
if (!comment || typeof comment !== 'object' || Array.isArray(comment)) {
throw new CommandExecutionError(`Zhihu answer comments contained a malformed ${role} row`);
}
const id = commentId(comment.id);
if (!id) throw new CommandExecutionError(`Zhihu answer comments contained a ${role} row without a stable id`);
const rootId = role === 'root' ? id : commentId(comment.reply_root_comment_id);
const parentId = role === 'root' ? '' : commentId(comment.reply_comment_id);
if (role === 'child' && rootId !== expectedRootId) {
throw new CommandExecutionError(`Zhihu answer comment ${id} had conflicting root provenance`);
}
if (role === 'child' && !parentId) {
throw new CommandExecutionError(`Zhihu answer comment ${id} did not identify its immediate parent`);
}
const signature = JSON.stringify({
role,
rootId,
parentId,
author: memberName(comment.author),
replyTo: role === 'child' ? memberName(comment.reply_to_author) : '',
content: stripHtml(comment.content || ''),
});
return { id, rootId, parentId, signature };
}
function addPageComments(byId, comments, role, expectedRootId) {
for (const comment of comments) {
const descriptor = describeComment(comment, role, expectedRootId);
const previous = byId.get(descriptor.id);View on GitHub (pinned to 49907e53dc)
Solutions
- Log the child's reply_root_comment_id and the expectedRootId to see whether the API re-parented the reply or the expected id is wrong.
- Refetch the child-comment page; if Zhihu moved the reply, fresh data should be consistent.
- If intentionally fetching replies of multiple roots in one pass, call fetchPages per root with the correct expectedRootId instead of merging pages.
- Upgrade the library if Zhihu changed reply provenance fields (e.g. reply_root_comment_id renamed).
Example fix
// before: fetching children of root '111' but page contains reply rooted at '222' fetchChildComments(page, '111', limit); // throws for the '222' row // after: fetch per root, or pre-filter const filtered = rows.filter(r => commentId(r.reply_root_comment_id) === '111');
Defensive patterns
Strategy: validation
Validate before calling
// ensure every child row's declared root matches the thread you are fetching
function filterChildrenForRoot(rows, expectedRootId) {
return rows.filter(r => String(r.reply_root_comment_id ?? '') === String(expectedRootId));
} Type guard
function belongsToRoot(child, rootId) {
return typeof child?.reply_root_comment_id === 'string' &&
child.reply_root_comment_id === String(rootId);
} Try / catch
try {
const replies = await fetchChildComments(page, rootId, limit);
} catch (err) {
if (String(err.message).includes('conflicting root provenance')) {
console.warn(`Root ${rootId} returned replies parented elsewhere; refetching`);
return fetchChildComments(page, rootId, limit); // single retry for re-parented data
}
throw err;
} Prevention
- Fetch replies one root at a time and never merge pages of different roots manually.
- Record reply_root_comment_id in your stored snapshots so re-parenting is detectable later.
- Avoid stale caches: use cache-control bypass for comment pages.
When it happens
Trigger: fetchChildComments requests replies for root X but the payload contains rows whose reply_root_comment_id normalizes to a different id than X. This happens when describeComment(child, 'child', expectedRootId) sees reply_root_comment_id null/absent/different.
Common situations: Zhihu re-parenting a reply (moved to another thread), caching layers serving stale mixed pages, or a bug in the caller passing the wrong expectedRootId when manually invoking fetchPages/fetchChildComments.
Related errors
- Zhihu answer comments returned conflicting data for comment
- Bilibili view API returned duplicate page entries for p=${pa
- semanticscholar citations row is missing citingPaper
- Xiaoyuzhou playback progress returned duplicate eid ${eid}
- Xiaoyuzhou playback progress pid did not match history eid $
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7196888d16b0090d.
Report an issue: GitHub.