{"record":{"id":"04ce9cb5224fd0e6","repo":"jackwener/OpenCLI","slug":"xiaohongshu-unfollow-failed-clickresult-reason","errorCode":null,"errorMessage":"xiaohongshu/unfollow failed: ${clickResult.reason ?? 'unknown reason'}","messagePattern":"xiaohongshu/unfollow failed: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/unfollow.js","lineNumber":235,"sourceCode":"                );\n            }\n            if (/\\/login(?:[/?#]|$)/i.test(parsedHref.pathname)) {\n                throw new AuthRequiredError('www.xiaohongshu.com');\n            }\n            const currentProfile = parsedHref.pathname.match(/^\\/user\\/profile\\/([a-zA-Z0-9]{8,32})\\/?$/);\n            if (currentProfile?.[1] !== userId) {\n                throw new CommandExecutionError(\n                    `xiaohongshu/unfollow: expected profile ${userId}, got ${parsedHref.pathname}`,\n                );\n            }\n\n            // Step 1: click 已关注 (idempotent — bails out if 关注 is visible)\n            const clickResult = requireActionResult(\n                await page.evaluate(buildClickUnfollowScript()),\n                'click-unfollow',\n            );\n            if (!clickResult.ok) {\n                throw new CommandExecutionError(\n                    `xiaohongshu/unfollow failed: ${clickResult.reason ?? 'unknown reason'}`,\n                );\n            }\n            if (clickResult.state === 'not-following') {\n                return [{ status: 'not-following', user_id: userId, url }];\n            }\n\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'})`,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/unfollow.js#L217-L253","documentation":"The in-page unfollow script returns {ok:false, state:'failed', reason} when it cannot find the follow-state button (已关注/已互关) on the profile. The CLI wraps that reason into this CommandExecutionError. Per the script, typical reasons include being logged out, being blocked/limited, or the site's DOM/class names changing so the button selectors no longer match.","triggerScenarios":"The profile page rendered without the follow-state button (logged-out view, restricted account, soft-ban/captcha overlay); xiaohongshu shipped a UI redesign changing button markup so SCOPE_SELECTORS/button-text matching fails; page content not settled when the script ran; viewing a profile where the button is hidden (e.g. self profile or blocked user).","commonSituations":"Long-running automation broken by a site UI update; heavy rate-limiting showing a verification wall instead of the profile; running on your own profile (no 已关注 button); network slow so the profile header hadn't rendered within PROFILE_SETTLE_MS.","solutions":["Read the reason in the message: 'Follow-state button not found' usually means logged out/blocked or selectors changed — open the profile manually in the automated Chrome and confirm the 已关注 button is visible","Re-login / clear any captcha or restriction, then retry","If the site UI changed, update SCOPE_SELECTORS/FOLLOWING_LABELS in buildClickUnfollowScript to match the new markup","Increase the settle wait or retry to let the profile fully render on slow networks; ensure you are not targeting your own profile"],"exampleFix":"// before: settle too short, header not rendered\nawait page.wait({ time: 2.5 });\n// after: allow longer settle / retry before evaluating\nawait page.wait({ time: 5 });\n// or retry the command once before assuming UI change","handlingStrategy":"try-catch","validationCode":"// pre-check the follow button exists on the page before invoking the command (in browser context)\nconst btn = [...document.querySelectorAll('button, [role=\"button\"]')].find(b => ['已关注','已互关','互相关注','关注'].includes((b.innerText||'').trim()));\nif (!btn) console.warn('Follow-state button not rendered yet — wait or expect failure');","typeGuard":"function isActionResult(v) { return !!v && typeof v === 'object' && typeof v.ok === 'boolean'; }","tryCatchPattern":"try { await cli.unfollow({ 'user-id': id }); } catch (e) { if (/unfollow failed:/.test(String(e.message))) { /* check reason: re-login if logged out, or update selectors if the site UI changed */ } else throw e; }","preventionTips":["Verify the profile renders the 已关注 button in the automated browser before running","Re-login and clear captchas/rate limits before batch unfollows","Throttle automation to avoid risk-control walls","After site UI changes, update SCOPE_SELECTORS and button labels in the injected scripts","Never run against your own profile (no follow button)"],"tags":["automation","dom-selector","page-state","ui-change"],"backgroundTag":"dom-element-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}