jackwener/OpenCLI · error · CommandExecutionError

Unexpected Rednote search extraction payload shape; expected

Error message

Unexpected Rednote search extraction payload shape; expected an array of rows.

What it means

requireSearchRows unwraps the page.evaluate payload (via unwrapEvaluateResult) and demands an array of result rows. When the unwrapped value is not an array it throws CommandExecutionError, because the extraction script's output contract changed or the page returned an error/other shape.

Source

Thrown at clis/rednote/search.js:26

import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
import { buildScrollUntilJs, buildSearchExtractJs, noteIdToDate } from '../xiaohongshu/search.js';
import { unwrapEvaluateResult } from '../xiaohongshu/shared.js';

function parseLimit(raw) {
    const parsed = Number(raw);
    if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {
        throw new ArgumentError(`--limit must be an integer between 1 and 100, got ${JSON.stringify(raw)}`);
    }
    if (parsed < 1 || parsed > 100) {
        throw new ArgumentError(`--limit must be between 1 and 100, got ${parsed}`);
    }
    return parsed;
}
function requireSearchRows(payload) {
    const rows = unwrapEvaluateResult(payload);
    if (!Array.isArray(rows)) {
        throw new CommandExecutionError('Unexpected Rednote search extraction payload shape; expected an array of rows.');
    }
    return rows;
}

/**
 * Wait for search results or login wall using MutationObserver (max 5s).
 *
 * Differs from xiaohongshu by detecting a full-screen login modal instead
 * of (and as a fallback, alongside) the inline `登录后查看搜索结果` text.
 * The modal detector filters hidden / zero-area elements to avoid false
 * positives on background dialogs.
 */
const WAIT_FOR_CONTENT_JS = `
  new Promise((resolve) => {
    const hasLoginModal = () => {
      const candidates = document.querySelectorAll(
        '[class*="login-modal"], [class*="LoginModal"], [class*="login-container"], [class*="LoginContainer"], dialog[role="dialog"]'
      );

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command; if it persists, check the page manually for a login wall and authenticate
  2. Add a wait-for-content step before extraction so rows are rendered first
  3. Log the raw payload with unwrapEvaluateResult to see the actual shape returned
  4. Update buildSearchExtractJs if the site's result markup/return shape changed

Example fix

// before
const rows = requireSearchRows(await page.evaluate(extractJs));
// after
await page.evaluate(WAIT_FOR_CONTENT_JS); // ensure results or detect login wall first
const rows = requireSearchRows(await page.evaluate(extractJs));
Defensive patterns

Strategy: type-guard

Validate before calling

const unwrapped = unwrapEvaluateResult(payload); if (!Array.isArray(unwrapped)) console.error('bad payload shape:', unwrapped);

Type guard

const isRowsPayload = (p) => Array.isArray(unwrapEvaluateResult(p));

Try / catch

try { return await searchRednote(query, limit); } catch (e) { if (e instanceof CommandExecutionError && /payload shape/.test(e.message)) { await waitForSearchContent(page); return searchRednote(query, limit); } throw e; }

Prevention

When it happens

Trigger: buildSearchExtractJs returning an object (e.g. {error:...}), unwrapEvaluateResult failing to unwrap a driver-specific wrapper, or the SPA replacing results with a login wall/error node mid-scroll.

Common situations: Rednote search hitting a login wall or CAPTCHA before results render, scrolling too fast so the script extracts a placeholder object, or a site frontend change altering the extract script's return type.

Related errors


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