jackwener/OpenCLI · critical · AuthRequiredError

Not logged into x.com (no ct0 cookie)

Error message

Not logged into x.com (no ct0 cookie)

What it means

After loading https://x.com/home the command inspects cookies for ct0, X's CSRF token which is only set for authenticated sessions. If no ct0 cookie exists, the session is not logged in, and scraping the followers API is impossible, so an AuthRequiredError is thrown. This is the library's explicit authentication precondition check.

Source

Thrown at clis/twitter/followers.js:109

    // when a dedicated follower count is needed.
    columns: ['screen_name', 'name', 'bio'],
    func: async (page, kwargs) => {
        const limit = kwargs.limit;
        if (!Number.isInteger(limit) || limit <= 0) {
            throw new ArgumentError('limit must be a positive integer');
        }

        const rawUser = String(kwargs.user ?? '').trim();
        let targetUser = normalizeScreenName(rawUser);
        if (rawUser && !targetUser) {
            throw new ArgumentError('twitter followers user must be a valid Twitter/X handle', 'Example: opencli twitter followers @elonmusk --limit 100');
        }
        await page.goto('https://x.com/home');
        await page.wait({ selector: '[data-testid="primaryColumn"]' });
        const cookies = await page.getCookies({ url: 'https://x.com' });
        const ct0 = cookies.find((cookie) => cookie.name === 'ct0')?.value || null;
        if (!ct0) {
            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
        }

        if (!targetUser) {
            // Bridge wraps primitive page.evaluate returns as { session, data:<value> };
            // unwrap so the href string is usable downstream.
            const href = unwrapBrowserResult(await page.evaluate(`() => {
                const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
                return link ? link.getAttribute('href') : null;
            }`));
            if (!href || typeof href !== 'string') {
                throw new AuthRequiredError('x.com', 'Could not find logged-in user profile link. Are you logged in?');
            }
            targetUser = normalizeScreenName(href);
            if (!targetUser) {
                throw new AuthRequiredError('x.com', 'Could not find logged-in user profile link. Are you logged in?');
            }
        }
        if (!targetUser) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the browser profile the tool uses, then re-run
  2. Point the tool at the persistent browser profile/user-data-dir containing your session
  3. Re-authenticate if your session expired (log in again to regenerate ct0)
  4. Verify the cookie exists: document.cookie or devtools Application tab for x.com

Example fix

// before (fresh headless context, no login)
page = await browser.newContext() // no x.com session
// after (reuse logged-in profile)
page = await browser.newContext({ storageState: 'xcom-session.json' })
Defensive patterns

Strategy: try-catch

Validate before calling

const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) {
  throw new Error('Run: opencli auth twitter  (or log into x.com in the browser profile)');
}

Type guard

null

Try / catch

try {
  const rows = await opencli.twitter.followers(user);
} catch (err) {
  if (err instanceof AuthRequiredError || /no ct0 cookie/.test(err.message)) {
    await loginToX(); // open browser for interactive login, then retry once
  } else throw err;
}

Prevention

When it happens

Trigger: Running the command with a browser profile that has never logged into x.com; cookies were cleared or expired; the browser context is fresh/incognito; x.com session was invalidated server-side.

Common situations: CI environments with no logged-in browser profile; forgetting to point the tool at the persistent profile where you logged in; X logged you out after a password change or security event.

Related errors


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