jackwener/OpenCLI · error · ArgumentError

xiaohongshu/follow: profile URL must be /user/profile/<userI

Error message

xiaohongshu/follow: profile URL must be /user/profile/<userId>

What it means

After validating scheme and host, assertUserId extracts the user ID from the path with /\/user\/profile\/([a-zA-Z0-9]{8,32})\/?$/. If the path doesn't match, ArgumentError is thrown. The URL is valid xiaohongshu.com but not a user profile page.

Source

Thrown at clis/xiaohongshu/follow.js:54

    }
    return inner;
}

function assertUserId(raw) {
    const input = String(raw ?? '').trim();
    if (/^https?:\/\//i.test(input)) {
        let parsed;
        try {
            parsed = new URL(input);
        } catch {
            throw new ArgumentError('xiaohongshu/follow: invalid profile URL');
        }
        if (parsed.protocol !== 'https:' || !isXiaohongshuHost(parsed.hostname)) {
            throw new ArgumentError('xiaohongshu/follow: profile URL must be an exact https://*.xiaohongshu.com URL');
        }
        const match = parsed.pathname.match(/^\/user\/profile\/([a-zA-Z0-9]{8,32})\/?$/);
        if (!match) {
            throw new ArgumentError('xiaohongshu/follow: profile URL must be /user/profile/<userId>');
        }
        return match[1];
    }
    const userId = normalizeXhsUserId(raw);
    if (!userId || !USER_ID_RE.test(userId)) {
        throw new ArgumentError(
            'xiaohongshu/follow: user-id must be a Xiaohongshu user ID (e.g. 5d8f88dc0000000001005d3a) or full profile URL',
        );
    }
    return userId;
}

/**
 * The injected page script. Lives in the browser context, so it can't import
 * anything — every helper is inlined. Returns `{ ok, state, reason? }` where
 * `state` is one of: 'followed' | 'already-following' | 'failed'.
 */
function buildFollowScript() {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Use the exact /user/profile/<userId> path: https://www.xiaohongshu.com/user/profile/5d8f88dc0000000001005d3a.
  2. Copy the ID directly from the profile page URL while viewing the user, stripping extra path segments.
  3. Pass the bare user ID instead of a URL to bypass path parsing entirely.

Example fix

// before
opencli xiaohongshu follow --user-id 'https://www.xiaohongshu.com/user/profile/5d8f88dc0000000001005d3a/tabs'
// after
opencli xiaohongshu follow --user-id '5d8f88dc0000000001005d3a'
Defensive patterns

Strategy: validation

Validate before calling

const USER_PROFILE_PATH = /^\/user\/profile\/([a-zA-Z0-9]{8,32})\/?$/;
function extractXhsUserId(raw) {
  const u = new URL(String(raw));
  const m = u.pathname.match(USER_PROFILE_PATH);
  return m ? m[1] : null;
}

Try / catch

try {
  await opencliFollow(url);
} catch (err) {
  if (err.code === 'ARGUMENT' && err.message.includes('/user/profile/<userId>')) {
    // wrong path shape — extract id or pass bare id
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing https://www.xiaohongshu.com/explore/<noteId> or /user/<something> (wrong path shape), a profile URL with extra segments like /user/profile/<id>/tabs, a userId shorter than 8 or longer than 32 chars, or a userId containing '-' or '_' (non-alphanumeric).

Common situations: Confusing note URLs with profile URLs; using mobile app share URLs with query junk in the path; URLs to /user/profile/ landing without an id; trailing content after the id beyond an optional slash.

Related errors


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