{"record":{"id":"ed35a9929c3ae9cf","repo":"jackwener/OpenCLI","slug":"xiaohongshu-search-masonry-remained-collapsed-afte","errorCode":null,"errorMessage":"Xiaohongshu search masonry remained collapsed after one fresh-tab recovery.","messagePattern":"Xiaohongshu search masonry remained collapsed after one fresh-tab recovery\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":895,"sourceCode":"        { name: 'note-type', type: 'string', default: 'all', choices: ['all', 'video', 'image'], help: 'Note type' },\n        { name: 'publish-time', type: 'string', default: 'anytime', choices: ['anytime', 'day', 'week', 'half-year'], help: 'Publish time range' },\n        { name: 'scope', type: 'string', default: 'all', choices: ['all', 'seen', 'unseen', 'following'], help: 'Search scope' },\n        { name: 'location', type: 'string', default: 'all', choices: ['all', 'same-city', 'nearby'], help: 'Location distance' },\n    ],\n    columns: ['rank', 'title', 'author', 'likes', 'published_at', 'url'],\n    func: async (page, kwargs) => {\n        try {\n            const limit = parseLimit(kwargs.limit);\n            const requestedFilters = resolveSearchFilters(kwargs);\n            const keyword = encodeURIComponent(kwargs.query);\n            const url = `https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`;\n            await page.goto(url);\n            let harvest = await collectSearchHarvest(page, limit, requestedFilters);\n            if (isCollapsedRender(harvest.diag)) {\n                await replaceCollapsedTab(page, url);\n                harvest = await collectSearchHarvest(page, limit, requestedFilters);\n                if (isCollapsedRender(harvest.diag)) {\n                    throw new CommandExecutionError(\n                        'Xiaohongshu search masonry remained collapsed after one fresh-tab recovery.',\n                        'Retry later or use a different logged-in browser session.',\n                    );\n                }\n            }\n            const rows = harvest.rows\n                .filter((item) => item.title)\n                .slice(0, limit);\n            if (rows.length === 0) {\n                throw new EmptyResultError('xiaohongshu search', 'No usable notes were rendered for this query.');\n            }\n            return rows\n                .map((item, i) => ({\n                rank: i + 1,\n                ...item,\n                published_at: noteIdToDate(item.url),\n            }));\n        }","sourceCodeStart":877,"sourceCodeEnd":913,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L877-L913","documentation":"Thrown when a Xiaohongshu search returned a collapsed masonry render, the library already attempted its one built-in recovery (replacing the tab with a fresh one and re-collecting), and the results were still collapsed. It means the site served a degraded/anti-bot or not-logged-in render even after recovery, so no usable masonry could be obtained.","triggerScenarios":"collectSearchHarvest() returns diag where isCollapsedRender(harvest.diag) is true, replaceCollapsedTab(page, url) succeeds, the harvest is re-run, and isCollapsedRender(harvest.diag) is still true.","commonSituations":"Xiaohongshu rate-limiting or risk control serving the collapsed view to the session; expired login cookies in the browser profile; scraping too frequently from the same session; site-side A/B changes to the collapsed page.","solutions":["Wait and retry later — the collapsed render is usually a temporary rate-limit/risk-control response.","Log in again or point the CLI at a different logged-in browser session (as the hint says).","Reduce query frequency / add delays between searches to avoid triggering risk control.","Verify the browser profile actually has an active xiaohongshu.com login by opening the site manually.","Update the library in case Xiaohongshu changed the collapsed-render markup that isCollapsedRender detects."],"exampleFix":"// before\nawait runSearch(query); // throws on collapsed render\n// after\nawait sleep(backoffMs);\ntry {\n  await runSearch(query);\n} catch (e) {\n  if (String(e).includes('remained collapsed')) await switchToFreshSessionAndRetry(query);\n  else throw e;\n}","handlingStrategy":"retry","validationCode":"// before searching, confirm the session can render a known page's masonry\nconst probe = await collectSearchHarvest(probePage, 1, {});\nif (isCollapsedRender(probe.diag)) throw new Error('session currently serving collapsed renders; wait before searching');","typeGuard":null,"tryCatchPattern":"try {\n  await xhsSearch(query);\n} catch (err) {\n  if (String(err.message).includes('remained collapsed')) {\n    await sleep(60_000);\n    return retryWithFreshSession(query);\n  }\n  throw err;\n}","preventionTips":["Throttle search frequency to avoid XHS risk control","Use a healthy logged-in browser session","Back off and retry later rather than hammering","Verify login state manually before batch runs"],"tags":["xhs","anti-bot","rate-limit","collapsed-render"],"backgroundTag":"collapsed-search-results","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}