jackwener/OpenCLI · error · CommandExecutionError

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

Error message

Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected string ${field}.

What it means

Each harvest row must contain string values for the fields title, author, likes, url, and author_url. When row[field] is not a string (missing, null, number, object), the CLI rejects the row because every consumer expects strings (likes is kept as its displayed string).

Source

Thrown at clis/xiaohongshu/search.js:297

    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) ||
        !diag || typeof diag !== 'object' || Array.isArray(diag) ||
        typeof diag.securityBlock !== 'boolean' || typeof diag.stopReason !== 'string' ||
        !Number.isFinite(diag.scrollHeight) || diag.scrollHeight < 0 ||
        !Number.isFinite(diag.clientHeight) || diag.clientHeight < 0 ||

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the search to rule out a partially rendered page.
  2. Update the CLI if xiaohongshu changed the card DOM so fields extract as null.
  3. Filter or treat ad/promoted cards differently — they often lack expected fields.
  4. Report persistent missing-field rows with the row contents for a harvest-script fix.
Defensive patterns

Strategy: type-guard

Type guard

function hasRequiredStringFields(row) {
  return ['title','author','likes','url','author_url']
    .every(f => typeof row?.[f] === 'string');
}

Try / catch

try {
  const rows = await searchNotes(query);
} catch (e) {
  if (/expected string \w+/.test(String(e.message))) {
    console.error('A result card lacked a field; retry or reduce result count:', e.message);
    return searchNotes(query, { limit: smaller });
  }
  throw e;
}

Prevention

When it happens

Trigger: A harvested row object at index+1 is missing one of the five required fields or has a non-string value — typically because the page node the script read was absent (e.g. no author element, hidden likes) so the script produced null/undefined.

Common situations: Search results with unusual card layouts (ads, video cards) lacking an author or likes element, DOM changes renaming classes, or partially-rendered results harvested before hydration finished.

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/b62c9d3445763c3d. Report an issue: GitHub.