{"record":{"id":"297c6d1ca908ebab","repo":"jackwener/OpenCLI","slug":"malformed-xiaohongshu-user-snapshot-user-store-wa","errorCode":null,"errorMessage":"Malformed Xiaohongshu user snapshot: user store was not found","messagePattern":"Malformed Xiaohongshu user snapshot: user store was not found","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/user.js","lineNumber":46,"sourceCode":"      return {\n        noteGroups: safeClone(rawNotes || []),\n        pageData: safeClone(rawPageData || {}),\n        storePresent: hasUserStore,\n        notesPresent: Array.isArray(rawNotes),\n        pageDataPresent: Boolean(rawPageData && typeof rawPageData === 'object' && Object.keys(rawPageData).length > 0),\n        loginWall: Boolean(onLoginPage || loggedInVal === false),\n      };\n    })()\n  `;\nasync function readUserSnapshot(page) {\n    return await page.evaluate(USER_SNAPSHOT_JS);\n}\nexport function assertReadableUserSnapshot(snapshot) {\n    if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {\n        throw new CommandExecutionError('Malformed Xiaohongshu user snapshot');\n    }\n    if (snapshot.storePresent !== true) {\n        throw new CommandExecutionError('Malformed Xiaohongshu user snapshot: user store was not found');\n    }\n    if (snapshot.notesPresent !== true || !Array.isArray(snapshot.noteGroups)) {\n        throw new CommandExecutionError('Malformed Xiaohongshu user snapshot: notes array was not found');\n    }\n}\n/** 展平 noteGroups 后的真实笔记条数。小红书 user store 的 notes 是 [tab[], tab[], ...]\n *  形态（每个 tab 一个数组），首屏笔记在其中某个 tab 里；这里数所有 tab 里的笔记总数。 */\nexport function countFlatNotes(snapshot) {\n    const groups = snapshot?.noteGroups;\n    if (!Array.isArray(groups))\n        return 0;\n    let n = 0;\n    for (const g of groups)\n        n += Array.isArray(g) ? g.length : (g ? 1 : 0);\n    return n;\n}\n/** 页面是否被登录墙挡（302 到 /login，或 user store loggedIn=false）。 */\nexport function isLoginWallSnapshot(snapshot) {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/user.js#L28-L64","documentation":"The snapshot came back as an object but its storePresent flag was not true, meaning USER_SNAPSHOT_JS could not find the user store (e.g. __INITIAL_STATE__.user) on the page. This is a known hydration race: the store is injected asynchronously by SSR/client bootstrap after page.goto, so evaluating too early intermittently fails (documented in the source as the 2026-06-09 bug fixed by adding page.wait).","triggerScenarios":"Evaluating USER_SNAPSHOT_JS immediately after page.goto before the client bootstrap injects __INITIAL_STATE__.user; landing on a page variant without the user store; login-walled or error page lacking app state.","commonSituations":"Slow network/CPU on first load; cold page with no cache; fetching a user profile right after navigation in a fresh browser context.","solutions":["Retry the command — the comment in user.js says hydration waits + retries were added precisely for this intermittent race","Wait for the store before evaluating (waitForSelector or wait-for-function on __INITIAL_STATE__.user)","Check for a login wall: if the page redirected to /login the store will never appear — re-authenticate instead of retrying","Slow network: increase the hydration wait duration"],"exampleFix":"// before\nconst snapshot = await readUserSnapshot(page);\n// after\nawait page.waitForFunction(() => window.__INITIAL_STATE__?.user, { timeout: 15000 });\nconst snapshot = await readUserSnapshot(page);","handlingStrategy":"retry","validationCode":"await page.waitForFunction(() => Boolean(window.__INITIAL_STATE__?.user), { timeout: 15000 });","typeGuard":"function hasUserStore(s) { return Boolean(s && typeof s === 'object' && s.storePresent === true); }","tryCatchPattern":"try {\n  await getUserProfile(url);\n} catch (err) {\n  if (/user store was not found/.test(err.message)) {\n    await sleep(3000); await retryWithBackoff(() => getUserProfile(url), 3);\n  } else throw err;\n}","preventionTips":["Always wait for __INITIAL_STATE__.user before evaluate (the documented hydration race fix)","Use retries with backoff for first-load scenarios on slow networks","Distinguish login-wall (use AuthRequiredError path) from hydration failure before retrying"],"tags":["hydration","race-condition","browser-automation","xiaohongshu"],"backgroundTag":"hydration-race-store-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}