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 device-follow resolver scrapes x.com in a browser page and reads the ct0 cookie, which x.com sets for authenticated sessions and which doubles as the CSRF token for the GraphQL API. If no ct0 cookie exists for https://x.com, the session is not logged in, so an AuthRequiredError is thrown before any API call is made.

Source

Thrown at clis/twitter/device-follow.js:137

cli({
    site: 'twitter',
    name: 'device-follow',
    access: 'read',
    description: 'Read the /i/timeline device-follow notification stream (tweets aggregated under a bell-icon "new posts from @userA and N others" notification)',
    domain: 'x.com',
    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'limit', type: 'int', default: 20, help: `Maximum number of tweets to return (1-${MAX_LIMIT}, default 20)` },
        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank by weighted engagement and return the top N. Default 0 keeps upstream ordering.' },
    ],
    columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],
    func: async (page, kwargs) => {
        const limit = parseLimit(kwargs.limit);
        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 apiUrl = buildDeviceFollowUrl(limit);
        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 data = await page.evaluate(`async () => {
        try {
          const r = await fetch("${apiUrl}", { method: "GET", headers: ${headers}, credentials: 'include' });
          if (!r.ok) return { error: r.status };
          try {
            return await r.json();
          } catch (e) {
            return { errorKind: 'non_json', detail: String(e && e.message || e) };
          }
        } catch (e) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the browser profile the CLI uses, then retry
  2. Confirm the CLI is using the intended browser profile/data directory that contains the x.com session
  3. Clear x.com cookies and log in again if the session is stale
  4. Verify cookies are reachable: page.getCookies({url:'https://x.com'}) should list a ct0 entry

Example fix

// before (fresh headless profile, not logged in)
cli twitter device-follow
// after
cli login-browser  # or open the managed profile and log into x.com first
cli twitter device-follow
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('Open the managed browser profile and log into x.com first');
}

Type guard

function hasCt0(cookies) {
  return Array.isArray(cookies) && cookies.some((c) => c && c.name === 'ct0' && !!c.value);
}

Try / catch

try {
  await cli.twitter.deviceFollow();
} catch (e) {
  if (e instanceof AuthRequiredError) {
    console.error('Not logged into x.com — open the CLI browser profile and sign in.');
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Running the device-follow command in a browser profile that has never logged into x.com, after the user logged out, after x.com cleared/rotated cookies, or when page.getCookies({url:'https://x.com'}) returns no cookie named 'ct0' (e.g. wrong profile or expired session).

Common situations: CI environments or headless browser profiles without a persisted logged-in session; a user recently changed passwords causing session invalidation; pointing the CLI at a fresh/empty browser data directory.

Related errors


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