{"record":{"id":"8dc79234850a6eb0","repo":"jackwener/OpenCLI","slug":"xiaohongshu-search-filter-detail","errorCode":null,"errorMessage":"xiaohongshu search filter ${detail}","messagePattern":"xiaohongshu search filter (.+?)","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":510,"sourceCode":"        return { status: 'ok' };\n      })()\n    `;\n}\n\nfunction requireFilterApplication(payload) {\n    const result = unwrapEvaluateResult(payload);\n    if (!result || typeof result !== 'object' || Array.isArray(result) || typeof result.status !== 'string') {\n        throw new CommandExecutionError('Unexpected Xiaohongshu search filter result shape.');\n    }\n    if (result.status === 'ok') {\n        return;\n    }\n    const detail = typeof result.detail === 'string' ? result.detail : 'unknown';\n    if (result.status === 'auth') {\n        throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search filters require a logged-in browser session');\n    }\n    if (result.status === 'timeout') {\n        throw new TimeoutError(`xiaohongshu search filter ${detail}`, FILTER_SETTLE_SECONDS);\n    }\n    if (result.status === 'location') {\n        throw new CommandExecutionError(`Xiaohongshu location filter was not applied (${detail}); enable browser geolocation permission.`);\n    }\n    if (result.status === 'capability') {\n        throw new CommandExecutionError(`Xiaohongshu account-scoped filter was unavailable (${detail}); verify login and account access.`);\n    }\n    if (result.status === 'inactive') {\n        throw new CommandExecutionError(`Xiaohongshu search filter chip did not become active (${detail}).`);\n    }\n    throw new CommandExecutionError(`Xiaohongshu search filter layout did not match the expected visible panel (${detail}).`);\n}\n/**\n * Build a \"scroll until enough or plateaued\" IIFE used in place of a fixed\n * `autoScroll({ times: N })`. Xiaohongshu's search results page lazy-loads\n * ~5-7 notes per scroll, so the previous `times: 2` capped extraction at\n * ~13 items regardless of `--limit` (see #1471). This helper drives scrolls\n * dynamically:","sourceCodeStart":492,"sourceCodeEnd":528,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L492-L528","documentation":"When the filter-application script reports status 'timeout', requireFilterApplication throws this TimeoutError with the script's detail string and FILTER_SETTLE_SECONDS as the duration. The filter control was clicked but the UI never reached the expected settled state (e.g. chip never activated or results never refreshed) within the allowed wait.","triggerScenarios":"The page returns {status:'timeout', detail:'sort-panel'} when a filter chip/panel did not settle in time — slow network, heavy page, or the chip's active state selector no longer matching after a Xiaohongshu UI change.","commonSituations":"Slow/throttled network or cold browser profile making the SPA sluggish; Xiaohongshu redesigning filter markup so the settle detector waits forever; CPU-starved CI environments running headless browsers.","solutions":["Retry the search; transient slowness often resolves on a second attempt","Increase headroom: run on a faster network/machine or with fewer concurrent browser tasks","If it reproduces consistently, check whether Xiaohongshu changed the filter panel DOM and update the injected script's selectors/settle detection","Catch TimeoutError specifically and re-run the harvest flow from page load"],"exampleFix":"// before\nawait applyFiltersAndHarvest(query, filters);\n// after\ntry {\n  await applyFiltersAndHarvest(query, filters);\n} catch (e) {\n  if (e instanceof TimeoutError) {\n    await applyFiltersAndHarvest(query, filters); // one retry\n    return;\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const applyWithRetry = async (attempt = 0) => {\n  try {\n    return await collectSearchHarvest(query, filters);\n  } catch (e) {\n    if (e instanceof TimeoutError && attempt < 2) {\n      await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));\n      return applyWithRetry(attempt + 1);\n    }\n    throw e;\n  }\n};","preventionTips":["Retry transient timeouts with backoff before giving up","Run the browser on a machine/network with adequate resources","Raise the settle window (FILTER_SETTLE_SECONDS) if your environment is consistently slow","After a Xiaohongshu UI change, update the settle-detection selectors in the injected script"],"tags":["timeout","scraping","filters","ui-settle"],"backgroundTag":"operation-timed-out","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}