{"record":{"id":"262b5a2e80e5d162","repo":"jackwener/OpenCLI","slug":"xiaohongshu-unfollow-confirmation-modal-step-fail","errorCode":null,"errorMessage":"xiaohongshu/unfollow: confirmation modal step failed (${confirmResult.kind ?? 'no kind reported'})","messagePattern":"xiaohongshu/unfollow: confirmation modal step failed \\((.+?)\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/unfollow.js","lineNumber":252,"sourceCode":"            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'})`,\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        }","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/unfollow.js#L234-L270","documentation":"The unfollow command's step 2 (clicking the confirm button in the unfollow confirmation modal) failed. The in-page script (buildConfirmModalScript) returned a result whose ok flag was false, and requireActionResult surfaced the modal's reported failure kind (e.g. modal not found, button not clickable). The library throws CommandExecutionError because without confirming the modal the unfollow action did not complete.","triggerScenarios":"The confirm modal did not appear within MODAL_SETTLE_MS, the modal DOM structure changed (site update), the confirm button selector no longer matches, or page.evaluate returned null/undefined so requireActionResult wrapped it as a failed 'confirm-modal' action.","commonSituations":"Xiaohongshu ships a frontend redesign changing modal markup; slow page/network makes the modal render after the settle wait; an anti-bot overlay intercepts clicks; the user was already unfollowed so no modal appears.","solutions":["Re-run the command once — slow hydration is often transient; the page.wait settle may just need more time","Re-check the modal script selectors in buildConfirmModalScript against the live xiaohongshu.com DOM and update them after site redesigns","Increase MODAL_SETTLE_MS or add an explicit wait-for-selector on the modal before evaluating","Confirm the session is valid and the target user is actually followed (already-unfollowed targets may skip the modal)"],"exampleFix":"// before\nawait page.wait({ time: MODAL_SETTLE_MS / 1000 });\nconst confirmResult = requireActionResult(\n    await page.evaluate(buildConfirmModalScript()),\n    'confirm-modal',\n);\n// after\nawait page.waitForSelector('.confirm-modal, [class*=confirm]', { timeout: 10000 });\nawait page.wait({ time: MODAL_SETTLE_MS / 1000 });\nconst confirmResult = requireActionResult(\n    await page.evaluate(buildConfirmModalScript()),\n    'confirm-modal',\n);","handlingStrategy":"try-catch","validationCode":"const modalReady = await page.evaluate(() => Boolean(document.querySelector('[class*=confirm], .modal')));\nif (!modalReady) throw new Error('confirm modal not rendered yet');","typeGuard":"function isConfirmResult(r) { return r && typeof r === 'object' && r.ok === true; }","tryCatchPattern":"try {\n  await unfollow(userId);\n} catch (err) {\n  if (/confirmation modal step failed/.test(err.message)) {\n    await page.reload(); await retryWithBackoff(() => unfollow(userId));\n  } else throw err;\n}","preventionTips":["Wait for the modal selector before evaluating instead of relying only on MODAL_SETTLE_MS","Re-verify modal selectors after xiaohongshu.com frontend deploys","Skip targets that are not currently followed to avoid no-modal cases"],"tags":["browser-automation","dom-selector","modal","xiaohongshu"],"backgroundTag":"modal-confirm-action-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}