{"record":{"id":"3854d4ba3e2b96bf","repo":"jackwener/OpenCLI","slug":"unexpected-xiaohongshu-search-harvest-row-index-3854d4","errorCode":null,"errorMessage":"Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.","messagePattern":"Unexpected Xiaohongshu search harvest row (.+?) author URL; expected a trusted profile URL\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":304,"sourceCode":"    }\n    catch {\n        return false;\n    }\n}\nfunction requireTrustedHarvestRow(row, index, webHost) {\n    if (!row || typeof row !== 'object' || Array.isArray(row)) {\n        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected an object.`);\n    }\n    for (const field of ['title', 'author', 'likes', 'url', 'author_url']) {\n        if (typeof row[field] !== 'string') {\n            throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected string ${field}.`);\n        }\n    }\n    if (!noteUrlInfo(row.url, webHost).key) {\n        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} URL; expected a trusted note URL.`);\n    }\n    if (!isTrustedAuthorUrl(row.author_url, webHost)) {\n        throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.`);\n    }\n    return row;\n}\nfunction requireHarvestPayload(payload, webHost) {\n    const result = unwrapEvaluateResult(payload);\n    const diag = result?.diag;\n    if (!result || typeof result !== 'object' || Array.isArray(result) || !Array.isArray(result.rows) ||\n        !diag || typeof diag !== 'object' || Array.isArray(diag) ||\n        typeof diag.securityBlock !== 'boolean' || typeof diag.stopReason !== 'string' ||\n        !Number.isFinite(diag.scrollHeight) || diag.scrollHeight < 0 ||\n        !Number.isFinite(diag.clientHeight) || diag.clientHeight < 0 ||\n        !Number.isSafeInteger(diag.cardCount) || diag.cardCount < 0 ||\n        !(diag.feedClientHeight === null || (Number.isFinite(diag.feedClientHeight) && diag.feedClientHeight >= 0)) ||\n        !Number.isSafeInteger(diag.distinctCardTops) || diag.distinctCardTops < 0) {\n        throw new CommandExecutionError('Unexpected Xiaohongshu search harvest payload shape; expected rows plus typed diagnostics.');\n    }\n    result.rows = result.rows.map((row, index) => requireTrustedHarvestRow(row, index, webHost));\n    return result;","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L286-L322","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Update isTrustedAuthorUrl / noteUrlInfo host allowlist if Xiaohongshu changed its profile URL format, and add the new format to the trust check","Re-run the search after dismissing any login, captcha, or interstitial overlay that corrupts harvested cards","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"],"exampleFix":"// before\nif (!isTrustedAuthorUrl(row.author_url, webHost)) {\n  throw new CommandExecutionError(`Unexpected Xiaohongshu search harvest row ${index + 1} author URL; expected a trusted profile URL.`);\n}\n// after\nif (!isTrustedAuthorUrl(row.author_url, webHost)) {\n  console.warn('skipping row with untrusted author_url:', row.author_url);\n  return null; // filter out instead of aborting the whole harvest\n}","handlingStrategy":"validation","validationCode":"function isTrustedAuthorUrlForCaller(url) {\n  try {\n    const u = new URL(url, 'https://www.xiaohongshu.com');\n    return (u.hostname === 'www.xiaohongshu.com' || u.hostname === 'xiaohongshu.com') && /\\/user\\/profile\\//.test(u.pathname);\n  } catch { return false; }\n}\n// check payload.rows.every((r, i) => isTrustedAuthorUrlForCaller(r.author_url)) before consuming rows","typeGuard":"const hasTrustedAuthor = (row) =>\n  typeof row?.author_url === 'string' &&\n  /^https:\\/\\/(www\\.)?xiaohongshu\\.com\\/user\\/profile\\//.test(row.author_url);","tryCatchPattern":"try {\n  const payload = requireHarvestPayload(raw, webHost);\n} catch (e) {\n  if (e.message.includes('expected a trusted profile URL')) {\n    // drop or quarantine the offending row; re-run the harvest if rows are missing\n  } else throw e;\n}","preventionTips":["Validate author_url shape on raw payloads before processing","Keep the host/path allowlist in sync with Xiaohongshu's current profile URL format","Dismiss login/captcha overlays before harvesting so cards render normally","Log untrusted rows instead of silently failing the entire harvest"],"tags":["validation","scraping","url-trust","xiaohongshu"],"backgroundTag":"untrusted-url-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}