jackwener/OpenCLI · warning · EmptyResultError

rednote/comments

Error message

rednote/comments

What it means

After navigating to the note page, the command evaluates buildCommentsExtractJs in the page. If the script returns null/undefined or a non-object (instead of the expected {results, ...} envelope), the command throws EmptyResultError('rednote/comments', 'Unexpected evaluate response') — the extraction could not produce any structured payload at all.

Source

Thrown at clis/rednote/comments.js:50

        { name: 'note-id', required: true, positional: true, help: 'Full rednote note URL with xsec_token' },
        { name: 'limit', type: 'int', default: 20, help: 'Number of top-level comments (max 50)' },
        { name: 'with-replies', type: 'boolean', default: false, help: 'Include nested replies; reply_to is the direct target shown by the page' },
    ],
    columns: ['rank', 'author', 'text', 'likes', 'time', 'is_reply', 'reply_to', 'images'],
    func: async (page, kwargs) => {
        const limit = parseCommentLimit(kwargs.limit);
        const withReplies = Boolean(kwargs['with-replies']);
        const raw = String(kwargs['note-id']);
        const noteId = parseNoteId(raw);
        await page.goto(buildNoteUrl(raw, {
            commandName: 'rednote comments',
            cookieRoot: 'rednote.com',
            signedUrlHint: REDNOTE_SIGNED_URL_HINT,
        }));
        await page.wait({ time: 2 + Math.random() * 3 });
        const data = await page.evaluate(buildCommentsExtractJs(withReplies, limit));
        if (!data || typeof data !== 'object') {
            throw new EmptyResultError('rednote/comments', 'Unexpected evaluate response');
        }
        if (data.securityBlock) {
            throw new CommandExecutionError('Rednote security block: the note detail page was blocked by risk control.', /^https?:\/\//.test(raw)
                ? 'The page may be temporarily restricted. Try again later or from a different session.'
                : 'Try using a full URL from search results (with xsec_token) instead of a bare note ID.');
        }
        if (data.loginWall) {
            throw new AuthRequiredError('www.rednote.com', 'Note comments require login');
        }
        void noteId;
        const all = normalizeCommentRows(data.results, 'rednote/comments');
        const toRow = (c, i) => ({
            rank: i + 1,
            author: c.author,
            text: c.text,
            likes: c.likes,
            time: c.time,
            is_reply: c.is_reply,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command — transient navigation or slow load often resolves on retry.
  2. Confirm the note URL is a full rednote.com note URL with a valid xsec_token (bare/expired links often land on error pages with no comments DOM).
  3. Increase effective wait/retry the navigation if the page loads slowly.
  4. Log in via the rednote auth flow — some notes require a session before the comments DOM renders.
  5. Check for a Rednote markup change and update buildCommentsExtractJs (clis/xiaohongshu/comments.js) selectors if the page layout changed.

Example fix

// before (single fast evaluate)
await page.wait({ time: 2 + Math.random() * 3 });
const data = await page.evaluate(buildCommentsExtractJs(withReplies, limit));
// after (retry once on empty response)
let data = await page.evaluate(buildCommentsExtractJs(withReplies, limit));
if (!data || typeof data !== 'object') {
  await page.wait(5);
  data = await page.evaluate(buildCommentsExtractJs(withReplies, limit));
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check the URL shape before invoking the command
function isFullRednoteNoteUrl(u) {
  return /^https?:\/\/www\.rednote\.com\/(explore|discovery\/item)\/[0-9a-f]+.*xsec_token=/.test(u);
}
if (!isFullRednoteNoteUrl(noteUrl)) throw new Error('Use a full rednote.com note URL including xsec_token');

Type guard

function isEvaluateResponse(d) {
  return typeof d === 'object' && d !== null
    && Array.isArray(d.results)
    && d.securityBlock === undefined && d.loginWall === undefined;
}

Try / catch

try {
  const rows = await run(['rednote', 'comments', noteUrl, '--limit', '20']);
} catch (err) {
  if (err instanceof EmptyResultError && /Unexpected evaluate response/.test(err.message)) {
    await sleep(3000);           // transient load/navigation often resolves it
    return run(['rednote', 'comments', noteUrl, '--limit', '20']);
  }
  throw err;
}

Prevention

When it happens

Trigger: page.evaluate returns undefined because the IIFE threw internally, the comments DOM root is absent (note deleted/region-locked), the page was replaced by an error/redirect interstitial, or the extraction script timed out / was interrupted before returning. Distinct from securityBlock/loginWall, which return structured flags — this fires only when there is no object at all.

Common situations: Rednote DOM restructuring breaking the extraction script so it throws; note URLs that 404 or redirect to a region-unavailable page; very slow loads where the 2–5s wait was insufficient and the script ran against an empty document; headless-detection pages serving no real content.

Related errors


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