jackwener/OpenCLI · error · CommandExecutionError

xiaohongshu/follow failed: ${result.reason ?? 'unknown reaso

Error message

xiaohongshu/follow failed: ${result.reason ?? 'unknown reason'}

What it means

Thrown when the in-page follow script runs but reports failure via result.ok === false. requireActionResult guarantees the payload shape, so reaching this throw means the DOM automation itself failed (button not found, click did not flip state, etc.) and the script returned a reason. The command surfaces result.reason, or 'unknown reason' when absent.

Source

Thrown at clis/xiaohongshu/follow.js:203

                    `xiaohongshu/follow: expected Xiaohongshu profile host, got ${parsedHref.hostname}`,
                );
            }
            if (/\/login(?:[/?#]|$)/i.test(parsedHref.pathname)) {
                throw new AuthRequiredError('www.xiaohongshu.com');
            }
            const currentProfile = parsedHref.pathname.match(/^\/user\/profile\/([a-zA-Z0-9]{8,32})\/?$/);
            if (currentProfile?.[1] !== userId) {
                throw new CommandExecutionError(
                    `xiaohongshu/follow: expected profile ${userId}, got ${parsedHref.pathname}`,
                );
            }

            const result = requireActionResult(
                await page.evaluate(buildFollowScript()),
                'follow-action',
            );
            if (!result.ok) {
                throw new CommandExecutionError(
                    `xiaohongshu/follow failed: ${result.reason ?? 'unknown reason'}`,
                );
            }
            return [{ status: result.state, user_id: userId, url }];
        } catch (err) {
            if (err instanceof CliError) throw err;
            throw new CommandExecutionError(
                `xiaohongshu/follow failed: ${err?.message ?? String(err)}`,
            );
        }
    },
});

export const __test__ = {
    assertUserId,
    buildFollowScript,
};

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read result.reason in the message and address the specific failure (e.g. button-not-found vs timeout).
  2. Retry — transient rendering delays commonly cause the state-flip timeout.
  3. Manually confirm the follow action works in the logged-in browser; re-login if the session is degraded.
  4. Update the CLI if XHS changed its profile-header DOM.

Example fix

// before
await cli.run(['xiaohongshu', 'follow', userId]); // flaky first attempt
// after
try {
  await cli.run(['xiaohongshu', 'follow', userId]);
} catch (e) {
  if (/timeout|button/i.test(e.message)) await cli.run(['xiaohongshu', 'follow', userId]); // one retry
  else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Confirm session is live before automating
const probe = await page.evaluate(() => !!document.querySelector('[role=button], button'));
if (!probe) throw new Error('page not ready for follow action');

Type guard

function isActionResult(v) {
  return !!v && typeof v === 'object' && !Array.isArray(v) && typeof v.ok === 'boolean';
}

Try / catch

try {
  await follow(userId);
} catch (err) {
  if (/follow failed/.test(err.message)) {
    await sleep(2000);
    await follow(userId); // single retry for transient DOM/timing issues
  } else throw err;
}

Prevention

When it happens

Trigger: page.evaluate(buildFollowScript()) returns { ok: false, reason } — the follow button was not located in the profile header, the button text never flipped to 已关注 within the 5s STATE_FLIP_TIMEOUT_MS, or the click was intercepted.

Common situations: User already blocked/restricted interactions; XHS DOM changed so selectors no longer match; page not fully settled when the script ran; network slowness exceeding the 5s poll window.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/cfb3f5b7f3850091. Report an issue: GitHub.