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

AuthRequiredError thrown before the search runs when the browser profile has no ct0 cookie for https://x.com. The ct0 cookie is X's CSRF token required to sign authenticated GraphQL requests (paired with the bearer token and X-Csrf-Token header), so its absence means the session is not logged in.

Source

Thrown at clis/twitter/search.js:289

        { name: 'product', type: 'string', choices: PRODUCT_CHOICES, help: 'Which X search tab to read: top (default), live (Latest), photos, videos. Maps to the f= URL param.' },
        { name: 'from', type: 'string', help: 'Restrict to tweets authored by <user>. Leading @ is stripped. Equivalent to appending `from:<user>` to the query.' },
        { name: 'has', type: 'string', choices: HAS_CHOICES, help: 'Restrict to tweets that have media|images|videos|links|replies. Maps to X\'s `filter:<has>` operator.' },
        { name: 'exclude', type: 'string', choices: EXCLUDE_CHOICES, help: 'Exclude tweets matching <type>: replies|retweets|media|links. Maps to X\'s `-filter:<x>` operator (retweets → -filter:nativeretweets).' },
        { name: 'limit', type: 'int', default: 15, help: 'Maximum number of tweets to return (default 15). Result count after server-side filtering.' },
        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the results by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps X\'s native ordering.' },
    ],
    columns: ['id', 'author', 'bio', 'text', 'created_at', 'likes', 'views', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],
    func: async (page, kwargs) => {
        const finalQuery = buildSearchQuery(kwargs.query, kwargs);
        if (!finalQuery) {
            throw new ArgumentError('twitter search query is empty', 'Provide a non-empty <query>, or use at least one of --from / --has / --exclude.');
        }
        if (!Number.isInteger(Number(kwargs.limit)) || Number(kwargs.limit) <= 0) {
            throw new ArgumentError('twitter search --limit must be a positive integer', 'Example: opencli twitter search opencli --limit 15');
        }
        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)');
        await page.goto('https://x.com/home', { waitUntil: 'load', settleMs: 1000 });
        const operation = await resolveTwitterOperationMetadata(page, 'SearchTimeline', SEARCH_TIMELINE_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',
            'Content-Type': 'application/json',
        });
        const product = resolveSearchProduct(kwargs);
        const results = [];
        const seen = new Set();
        let cursor = null;
        // Runaway guard only; --limit and cursor exhaustion control normal pagination.
        for (let i = 0; i < MAX_PAGINATION_PAGES && results.length < kwargs.limit; i++) {
            const fetchCount = Number(kwargs.limit) - results.length + 10;
            const [requestUrl, requestPayload] = buildSearchTimelineRequest(operation, finalQuery, product, fetchCount, cursor);
            const requestBody = JSON.stringify(requestPayload);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the browser session opencli uses, then re-run the command
  2. Refresh/restore the session cookies (ct0 and auth_token)
  3. Point opencli at the correct browser profile that holds the x.com session
  4. Re-run `opencli login`/auth flow if the CLI provides one

Example fix

// before
opencli twitter search opencli  // AuthRequiredError: no ct0 cookie
// after
opencli login   # or manually log into x.com in the controlled browser
opencli twitter search opencli
Defensive patterns

Strategy: validation

Validate before calling

const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.find((c) => c.name === 'ct0')) {
  throw new Error('Not logged into x.com — run the login flow first');
}

Type guard

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

Try / catch

try {
  await opencli.twitter.search(q);
} catch (e) {
  if (e.name === 'AuthRequiredError') {
    await runXLoginFlow();   // open x.com, log in, persist cookies
    await opencli.twitter.search(q);
  } else throw e;
}

Prevention

When it happens

Trigger: Running `opencli twitter search ...` with a fresh/incognito browser profile, after x.com logout, after cookies were cleared or expired, or when page.getCookies returns no x.com cookies.

Common situations: First run without ever logging into x.com in the automation browser, cookie purge by browser settings, logging out manually, or pointing opencli at the wrong profile directory.

Related errors


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