jackwener/OpenCLI · error · CommandExecutionError

Zhihu answer comments contained a ${role} row without a stab

Error message

Zhihu answer comments contained a ${role} row without a stable id

What it means

describeComment validates each comment row fetched from Zhihu's answer comments API. A row that is a valid object but whose comment.id cannot be normalized to a non-empty stable identifier (via commentId) is rejected. The library throws because every subsequent operation (dedup, tree building, provenance checks) keys off this id; a comment without one cannot be processed safely.

Source

Thrown at clis/zhihu/answer-comments-helpers.js:94

        }
        if (status === 404 && notFoundDetail) throw new EmptyResultError('zhihu answer-comments', notFoundDetail);
        if (status >= 400) throw new CommandExecutionError(`Zhihu ${label} request failed (HTTP ${status})`);
        throw new CommandExecutionError(`Zhihu ${label} returned an error payload: ${payload.__errorMessage || code}`);
    }
    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 };
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the offending row in the payload and confirm the comment.id field exists and normalizes to a non-empty value.
  2. Update this library to a version matching Zhihu's current comment_v5 API schema if the field name changed.
  3. Filter out rows with missing ids at the network/mock layer before they reach fetchPages, if you own the fetching boundary.
  4. Retry the fetch; if the API is returning a transient malformed page, a fresh request may contain valid rows.

Example fix

// before: mock fixture row without id
const rows = [{ author: { member: { name: 'alice' } }, content: 'hi' }];
// after
const rows = [{ id: '14839201', author: { member: { name: 'alice' } }, content: 'hi' }];
Defensive patterns

Strategy: validation

Validate before calling

// validate fixtures/responses before calling the API-driven code
function assertCommentRows(rows) {
  for (const r of rows) {
    if (r && typeof r === 'object' && !Array.isArray(r) && !String(r.id ?? '').trim()) {
      throw new Error(`fixture row missing comment.id: ${JSON.stringify(r).slice(0, 80)}`);
    }
  }
}

Type guard

function hasStableId(row) {
  return !!row && typeof row === 'object' && !Array.isArray(row) &&
    typeof row.id !== 'undefined' && row.id !== null && String(row.id).trim() !== '';
}

Try / catch

try {
  const comments = await fetchAnswerComments(page, answerId);
} catch (err) {
  if (String(err.message).includes('without a stable id')) {
    console.warn('Skipping page with malformed comment row (missing id)');
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: Zhihu's API returned a data row where comment.id is null, undefined, empty string, or a value commentId() normalizes to falsy (e.g. whitespace-only or non-numeric id) while the row is still an object.

Common situations: Zhihu schema drift (field renamed from id to id_string or similar), deleted/soft-removed comments appearing in a page payload, or proxied/cached responses with rows stripped of their id. Developers also hit it when mocking the API with fixtures missing the id field.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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