jackwener/OpenCLI · error · AuthRequiredError

Not logged into x.com (no ct0 cookie)

Error message

Not logged into x.com (no ct0 cookie)

What it means

The UserMedia GraphQL flow authenticates with the browser's ct0 cookie as the X-CSRF-Token header. Before querying, the code reads cookies for https://x.com and throws AuthRequiredError if no ct0 cookie exists, because x.com only issues ct0 to logged-in sessions and GraphQL calls would fail without it.

Source

Thrown at clis/twitter/download.js:351

            if (!username) {
                throw new ArgumentError('twitter download username must be a valid Twitter/X handle', 'Example: opencli twitter download @jack --limit 20');
            }
            return downloadUserMedia(page, username, limit, output);
        }
        catch (err) {
            if (err instanceof CliError) throw err;
            throw new CommandExecutionError(`twitter download failed: ${err?.message ?? String(err)}`);
        }
    },
});

async function downloadUserMedia(page, username, limit, output) {
    await page.goto(`https://x.com/${username}`);
    await page.wait({ selector: '[data-testid="primaryColumn"]' });

    const cookies = await page.getCookies({ url: 'https://x.com' });
    const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
    if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');

    const userMediaOperation = await resolveTwitterOperationMetadata(page, 'UserMedia', USER_MEDIA_OPERATION);
    const userByScreenNameOperation = await resolveTwitterOperationMetadata(page, 'UserByScreenName', USER_BY_SCREEN_NAME_OPERATION);

    const headers = JSON.stringify({
        'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
        'X-Csrf-Token': ct0,
        'X-Twitter-Auth-Type': 'OAuth2Session',
        'X-Twitter-Active-User': 'yes',
    });

    const ubsUrl = buildUserByScreenNameUrl(userByScreenNameOperation, username);
    const userLookup = requireFetchPayload(await page.evaluate(`async () => {
      try {
        const resp = await fetch("${ubsUrl}", { headers: ${headers}, credentials: 'include' });
        if (!resp.ok) return { ok: false, status: resp.status };
        const payload = await resp.json();
        return { ok: true, payload };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the browser profile the CLI uses, then re-run the command
  2. Verify the CLI is pointed at the correct browser profile/user-data-dir containing the logged-in session
  3. Clear cookies and log in again if the session is corrupt or expired
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

const hasCt0 = (cookies) => Array.isArray(cookies) && cookies.some((c) => c.name === 'ct0' && c.value);

Try / catch

try {
  await downloadUserMedia(page, username, limit);
} catch (err) {
  if (err.name === 'AuthRequiredError') {
    console.error('Open a browser on x.com and log in, then retry.');
  }
}

Prevention

When it happens

Trigger: Running `opencli twitter download <username>` with a browser profile that has never logged into x.com, after logging out, or with cookies cleared/expired so no ct0 cookie is present.

Common situations: Fresh automation browser profile without a login; session expired (x.com rotates/invalidates ct0); pointing the CLI at the wrong profile directory; corporate proxy stripping cookies.

Related errors


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