{"record":{"id":"d873c5b7463b7cf5","repo":"jackwener/OpenCLI","slug":"xiaohongshu-unfollow-verifyraw-reason-stat","errorCode":null,"errorMessage":"xiaohongshu/unfollow: ${verifyRaw?.reason ?? 'state verification failed'}","messagePattern":"xiaohongshu/unfollow: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/unfollow.js","lineNumber":260,"sourceCode":"\n            // Step 2: confirm the unfollow modal. Wait for the modal to mount\n            // first — xhs uses a CSS transition before the footer becomes\n            // interactive.\n            await page.wait({ time: MODAL_SETTLE_MS / 1000 });\n            const confirmResult = requireActionResult(\n                await page.evaluate(buildConfirmModalScript()),\n                'confirm-modal',\n            );\n            if (!confirmResult.ok) {\n                throw new CommandExecutionError(\n                    `xiaohongshu/unfollow: confirmation modal step failed (${confirmResult.kind ?? 'no kind reported'})`,\n                );\n            }\n\n            // Step 3: verify the profile button text flipped back to 关注.\n            const verifyRaw = unwrapEvaluateResult(await page.evaluate(buildVerifyFollowFlippedScript()));\n            if (!verifyRaw || typeof verifyRaw !== 'object' || verifyRaw.ok !== true) {\n                throw new CommandExecutionError(\n                    `xiaohongshu/unfollow: ${verifyRaw?.reason ?? 'state verification failed'}`,\n                );\n            }\n            return [{ status: 'unfollowed', user_id: userId, url }];\n        } catch (err) {\n            if (err instanceof CliError) throw err;\n            throw new CommandExecutionError(\n                `xiaohongshu/unfollow failed: ${err?.message ?? String(err)}`,\n            );\n        }\n    },\n});\n\nexport const __test__ = {\n    assertUserId,\n    buildClickUnfollowScript,\n    buildConfirmModalScript,\n    buildVerifyFollowFlippedScript,","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/unfollow.js#L242-L278","documentation":"After unfollowing, step 3 re-evaluates the profile page (buildVerifyFollowFlippedScript) to confirm the follow button text flipped back to 关注. The script returned an object without ok===true and reported its own reason (or none), so the command throws CommandExecutionError. This is a post-action state verification: the click succeeded but the page state did not reflect the unfollow.","triggerScenarios":"The verify script found the button still showing 已关注 (unfollow did not actually take effect), the button markup changed so the text check failed, or the page had not re-rendered yet when the script ran.","commonSituations":"Site redesign changes button label/structure; server accepted the unfollow but UI lags; rate limiting silently rejects the unfollow API call; stale cached page after evaluate.","solutions":["Re-run the command — check if the unfollow actually persisted on the site (the click may have worked despite verification failing)","Add a small wait/retry between the confirm click and the verify evaluate to allow the UI to re-render","Update buildVerifyFollowFlippedScript's button-text/selector expectations after any xiaohongshu.com frontend change","Verify the account session is healthy (rate-limited or expired sessions can silently no-op the unfollow)"],"exampleFix":"// before\nconst verifyRaw = unwrapEvaluateResult(await page.evaluate(buildVerifyFollowFlippedScript()));\nif (!verifyRaw || typeof verifyRaw !== 'object' || verifyRaw.ok !== true) {\n// after\nlet verifyRaw;\nfor (let attempt = 0; attempt < 3; attempt++) {\n    verifyRaw = unwrapEvaluateResult(await page.evaluate(buildVerifyFollowFlippedScript()));\n    if (verifyRaw?.ok === true) break;\n    await page.wait({ time: 1 });\n}\nif (!verifyRaw || typeof verifyRaw !== 'object' || verifyRaw.ok !== true) {","handlingStrategy":"retry","validationCode":"const btn = await page.evaluate(() => document.body.innerText.includes('已关注'));\nif (!btn) throw new Error('target does not appear followed; unfollow unnecessary');","typeGuard":"function isVerifiedFlip(v) { return v && typeof v === 'object' && v.ok === true; }","tryCatchPattern":"try {\n  await unfollow(userId);\n} catch (err) {\n  if (/state verification failed|unfollow: /.test(err.message)) {\n    await wait(2000); // allow UI re-render, then verify manually or retry\n  } else throw err;\n}","preventionTips":["Add a short delay between confirm click and verify evaluate","Confirm the unfollow persisted via a second page load before failing hard","Update button-text expectations after site redesigns"],"tags":["browser-automation","state-verification","xiaohongshu"],"backgroundTag":"post-action-state-verification-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}