{"record":{"id":"b62c9d3445763c3d","repo":"jackwener/OpenCLI","slug":"unexpected-xiaohongshu-search-harvest-row-index-b62c9d","errorCode":null,"errorMessage":"Unexpected Xiaohongshu search harvest row ${index + 1} shape; expected string ${field}.","messagePattern":"Unexpected Xiaohongshu search harvest row (.+?) shape; expected string (.+?)\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":297,"sourceCode":"    if (url === '')\n        return true;\n    try {\n        const parsed = new URL(url);\n        return parsed.protocol === 'https:' &&\n            parsed.hostname.toLowerCase() === webHost.toLowerCase() &&\n            /^\\/user\\/profile\\/[^/]+\\/?$/i.test(parsed.pathname);\n    }\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 ||","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L279-L315","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the search to rule out a partially rendered page.","Update the CLI if xiaohongshu changed the card DOM so fields extract as null.","Filter or treat ad/promoted cards differently — they often lack expected fields.","Report persistent missing-field rows with the row contents for a harvest-script fix."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasRequiredStringFields(row) {\n  return ['title','author','likes','url','author_url']\n    .every(f => typeof row?.[f] === 'string');\n}","tryCatchPattern":"try {\n  const rows = await searchNotes(query);\n} catch (e) {\n  if (/expected string \\w+/.test(String(e.message))) {\n    console.error('A result card lacked a field; retry or reduce result count:', e.message);\n    return searchNotes(query, { limit: smaller });\n  }\n  throw e;\n}","preventionTips":["Retry with a smaller result set to skip ad/promoted cards that often lack fields.","Re-run the search to rule out partially rendered pages.","Update the CLI if the card DOM changed so fields extract as null.","Note that likes is expected as a string, not a number, when pre-processing results."],"tags":["validation","schema","scraping","xiaohongshu"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}