jackwener/OpenCLI · error · CommandExecutionError

Unexpected Xiaohongshu search harvest row ${index + 1} shape

Error message

Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected an object.

What it means

requireTrustedHarvestRow validates each row harvested from the xiaohongshu search page. A row must be a non-null, non-array object; anything else (null, a string, an array, a number) is rejected because the downstream pipeline assumes object-shaped rows with known fields.

Source

Thrown at clis/xiaohongshu/search.js:293

    return results;
}

function isTrustedAuthorUrl(url, webHost) {
    if (url === '')
        return true;
    try {
        const parsed = new URL(url);
        return parsed.protocol === 'https:' &&
            parsed.hostname.toLowerCase() === webHost.toLowerCase() &&
            /^\/user\/profile\/[^/]+\/?$/i.test(parsed.pathname);
    }
    catch {
        return false;
    }
}
function requireTrustedHarvestRow(row, index, webHost) {
    if (!row || typeof row !== 'object' || Array.isArray(row)) {
        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected an object.`);
    }
    for (const field of ['title', 'author', 'likes', 'url', 'author_url']) {
        if (typeof row[field] !== 'string') {
            throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected string ${field}.`);
        }
    }
    if (!noteUrlInfo(row.url, webHost).key) {
        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} URL; expected a trusted note URL.`);
    }
    if (!isTrustedAuthorUrl(row.author_url, webHost)) {
        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.`);
    }
    return row;
}
function requireHarvestPayload(payload, webHost) {
    const result = unwrapEvaluateResult(payload);
    const diag = result?.diag;
    if (!result || typeof result !== 'object' || Array.isArray(result) || !Array.isArray(result.rows) ||

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the search — transient anti-bot pages can corrupt one harvest.
  2. Check the CLI version against recent xiaohongshu search DOM changes and update.
  3. Reduce result count or add delays to avoid truncated/partial harvests.
  4. Report the row shape from verbose logs if it persists — the harvest script needs a fix.
Defensive patterns

Strategy: try-catch

Type guard

function isHarvestRow(row) {
  return row !== null && typeof row === 'object' && !Array.isArray(row);
}

Try / catch

try {
  const rows = await searchNotes(query);
} catch (e) {
  if (String(e.message).includes('harvest row') && String(e.message).includes('expected an object')) {
    // retry once; anti-bot pages can corrupt a single harvest
    return searchNotes(query);
  }
  throw e;
}

Prevention

When it happens

Trigger: The in-page harvest script returned malformed data for row index+1 — e.g. a page scrape produced strings instead of objects, or the extraction script's shape contract drifted from what the page rendered.

Common situations: Xiaohongshu changed search-result markup so the harvest script collects wrong nodes, an anti-bot interstitial replaced results with junk, or a bug in the injected harvest script maps rows to arrays/strings.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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