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
- Re-run the search — transient anti-bot pages can corrupt one harvest.
- Check the CLI version against recent xiaohongshu search DOM changes and update.
- Reduce result count or add delays to avoid truncated/partial harvests.
- 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
- Retry searches that fail on a single row — transient anti-bot pages are a common cause.
- Keep the CLI updated when xiaohongshu changes search-result markup.
- Avoid scraping at high frequency, which invites anti-bot interstitials.
- Report persistent shape errors with verbose logs.
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
- Unexpected Xiaohongshu search harvest row ${index + 1} shape
- ${commandName}: malformed comment row ${field}
- ${commandName}: malformed comment row images
- ${commandName}: malformed comments payload
- ${commandName}: malformed comment row at index ${index}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a269107e3005f3b2.
Report an issue: GitHub.