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

thread.js:128 requires an authenticated x.com session before calling the TweetDetail GraphQL API. It reads cookies for https://x.com via CDP and looks for the ct0 cookie, which x.com sets as the CSRF token for logged-in sessions. If no ct0 cookie exists, AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)') is thrown because the authenticated GraphQL request would otherwise fail with a 403/404.

Source

Thrown at clis/twitter/thread.js:128

    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'tweet-id', positional: true, type: 'string', required: true, help: 'Tweet numeric ID (e.g. 1234567890) or full status URL' },
        { name: 'limit', type: 'int', default: 50 },
        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the thread by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps the conversation\'s structural ordering.' },
    ],
    columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],
    func: async (page, kwargs) => {
        let tweetId = kwargs['tweet-id'];
        const urlMatch = tweetId.match(/\/status\/(\d+)/);
        if (urlMatch)
            tweetId = urlMatch[1];
        // Cookie context auto-established by framework pre-nav (Strategy.COOKIE + domain).
        // Read CSRF token directly from the cookie store via CDP — zero page.evaluate round-trip.
        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)');
        // Build auth headers in TypeScript
        const headers = JSON.stringify({
            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
            'X-Csrf-Token': ct0,
            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });
        // Paginate — fetch in browser, parse in TypeScript
        const allTweets = [];
        const seen = new Set();
        let cursor = null;
        for (let i = 0; i < 5; i++) {
            const apiUrl = buildTweetDetailUrl(tweetId, cursor);
            // Browser-side: fetch + JSON parse with HTML-as-JSON sniffer so a
            // login wall / WAF page surfaces as a structured LoginWallError
            // instead of `SyntaxError: Unexpected token '<'`.
            const data = throwIfLoginWall(await page.evaluate(`async () => {
        ${BROWSER_JSON_SNIFF_FN}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the browser profile the CLI drives (run the CLI once with a visible browser and complete the login), then retry the command
  2. Verify the ct0 cookie exists: in DevTools on x.com, check Application > Cookies > https://x.com for a ct0 entry
  3. Ensure the CLI's cookie/auth strategy targets the x.com domain, not twitter.com, and that cookies are being loaded into that context
  4. Re-export or refresh the saved session/cookies if you import them from another tool, since ct0 rotates with sessions

Example fix

// before (logged-out profile)
opencli twitter thread https://x.com/jack/status/123
// after: login first in the driven browser profile
await page.goto('https://x.com/login'); // complete manual login, then rerun
opencli twitter thread https://x.com/jack/status/123
Defensive patterns

Strategy: try-catch

Validate before calling

const cookies = await page.getCookies({ url: 'https://x.com' });
const hasCt0 = cookies.some((c) => c.name === 'ct0' && c.value);
if (!hasCt0) {
  console.error('Not logged into x.com — run the CLI with a visible browser and log in first.');
  process.exit(1);
}

Type guard

function hasCt0Cookie(cookies) {
  return Array.isArray(cookies)
    && cookies.some((c) => c.name === 'ct0' && typeof c.value === 'string' && c.value.length > 0);
}

Try / catch

import { AuthRequiredError } from '@jackwener/opencli/errors';
try {
  await fetchThread(tweetUrl);
} catch (e) {
  if (e instanceof AuthRequiredError) {
    console.error(`${e.domain} login required: ${e.message}. Re-authenticate and retry.`);
  } else throw e;
}

Prevention

When it happens

Trigger: Running `opencli twitter thread <url>` (or any command reaching thread.js:128) while the browser profile used by the CLI is logged out of x.com, the session expired and cookies were rotated/cleared, cookies exist for twitter.com but not x.com, or the framework pre-nav to x.com was blocked.

Common situations: Fresh CI/machine without a logged-in browser profile; x.com logged the session out (password change, security sweep); user cleared cookies; using the wrong browser profile directory; x.com domain migration leaving stale twitter.com-only cookies.

Related errors


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