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 list-create command authenticates to x.com by reading the ct0 CSRF cookie from the browser session. If no ct0 cookie exists after navigating to https://x.com, the user is not logged in, so the command throws AuthRequiredError instead of making authenticated GraphQL calls that would fail anyway.

Source

Thrown at clis/twitter/list-create.js:115

    description: 'Create a new Twitter/X list (returns the new list id)',
    access: 'write',
    domain: 'x.com',
    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'name', positional: true, type: 'string', required: true, help: `List name (max ${NAME_MAX} chars)` },
        { name: 'description', type: 'string', default: '', help: `Optional list description (max ${DESCRIPTION_MAX} chars)` },
        { name: 'mode', type: 'string', default: 'public', help: 'public | private' },
    ],
    columns: ['id', 'name', 'description', 'mode', 'status'],
    func: async (page, kwargs) => {
        const { listName: name, listDescription: description, listMode: mode, privateFlag: isPrivate } = parseListCreateArgs(kwargs);

        await page.goto('https://x.com');
        await page.wait(3);
        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)');

        // Hardcode queryId: it must match the FEATURES schema below.
        // Letting resolveTwitterQueryId() drift would pull a newer queryId
        // whose schema would reject our simplified features payload.
        const queryId = CREATE_LIST_QUERY_ID;

        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 body = JSON.stringify({
            variables: { isPrivate, name, description },
            features: FEATURES,
            queryId,
        });

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the automation browser session, then re-run the command
  2. Re-run `opencli` login/session bootstrap for Twitter if the CLI provides one
  3. Check you are using the same browser profile that holds the x.com session
  4. Clear corrupted cookies and log in again if ct0 is stale

Example fix

// before
opencli twitter list-create "My List"
// after
opencli twitter login   # establish x.com session first
opencli twitter list-create "My List"
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) throw new Error('Not logged into x.com — run login first');

Type guard

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

Try / catch

try {
  await runListCreate(page, kwargs);
} catch (e) {
  if (e instanceof AuthRequiredError && /ct0/.test(e.message)) {
    console.error('Run `opencli twitter login` and retry.');
    process.exitCode = 2;
  } else throw e;
}

Prevention

When it happens

Trigger: page.getCookies({ url: 'https://x.com' }) returns no cookie named 'ct0' after page.goto('https://x.com') — i.e. no active x.com session in the automation browser.

Common situations: Running the CLI without ever logging into x.com in the automation browser; session expired/logged out; cookies cleared or isolated browser profile; incognito profile without stored cookies.

Related errors


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