jackwener/OpenCLI · error · CommandExecutionError

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

Error message

Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.

What it means

requireTrustedHarvestRow validates each row returned by the in-browser harvest script after a Xiaohongshu search scrape. When a row's author_url does not pass isTrustedAuthorUrl (i.e. it is not a profile URL on the trusted xiaohongshu web host), the function throws this CommandExecutionError. This guards against poisoned or malformed harvested data being trusted downstream.

Source

Thrown at clis/xiaohongshu/search.js:304

    }
    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 ||
        !Number.isSafeInteger(diag.cardCount) || diag.cardCount < 0 ||
        !(diag.feedClientHeight === null || (Number.isFinite(diag.feedClientHeight) && diag.feedClientHeight >= 0)) ||
        !Number.isSafeInteger(diag.distinctCardTops) || diag.distinctCardTops < 0) {
        throw new CommandExecutionError('Unexpected Xiaohongshu search harvest payload shape; expected rows plus typed diagnostics.');
    }
    result.rows = result.rows.map((row, index) => requireTrustedHarvestRow(row, index, webHost));
    return result;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the harvested row's author_url in the payload and confirm it is an absolute https URL on the trusted xiaohongshu web host pointing to a profile page
  2. Update isTrustedAuthorUrl / noteUrlInfo host allowlist if Xiaohongshu changed its profile URL format, and add the new format to the trust check
  3. Re-run the search after dismissing any login, captcha, or interstitial overlay that corrupts harvested cards
  4. Log the offending row (index and author_url) before throwing to aid diagnosis, then harden the harvest script to skip rows without a trusted author link

Example fix

// before
if (!isTrustedAuthorUrl(row.author_url, webHost)) {
  throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.`);
}
// after
if (!isTrustedAuthorUrl(row.author_url, webHost)) {
  console.warn('skipping row with untrusted author_url:', row.author_url);
  return null; // filter out instead of aborting the whole harvest
}
Defensive patterns

Strategy: validation

Validate before calling

function isTrustedAuthorUrlForCaller(url) {
  try {
    const u = new URL(url, 'https://www.xiaohongshu.com');
    return (u.hostname === 'www.xiaohongshu.com' || u.hostname === 'xiaohongshu.com') && /\/user\/profile\//.test(u.pathname);
  } catch { return false; }
}
// check payload.rows.every((r, i) => isTrustedAuthorUrlForCaller(r.author_url)) before consuming rows

Type guard

const hasTrustedAuthor = (row) =>
  typeof row?.author_url === 'string' &&
  /^https:\/\/(www\.)?xiaohongshu\.com\/user\/profile\//.test(row.author_url);

Try / catch

try {
  const payload = requireHarvestPayload(raw, webHost);
} catch (e) {
  if (e.message.includes('expected a trusted profile URL')) {
    // drop or quarantine the offending row; re-run the harvest if rows are missing
  } else throw e;
}

Prevention

When it happens

Trigger: The browser-side evaluate script returned a row whose author_url is missing, empty, relative, points at a non-profile page, or is on an unexpected host; row 4841-style payload passes URL validation but the author link failed the trust check.

Common situations: Xiaohongshu DOM changes so author links are rendered differently (e.g. relative '/user/profile/...' paths instead of absolute URLs), a card lacks an author link (anonymous/deleted account), or a bot-check/interstitial page injects unexpected markup that the harvest script captures as a row.

Related errors


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