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

list-delete reads the ct0 CSRF cookie from the x.com session to build authenticated API headers. If the cookie is absent, the session is not logged in and the command throws AuthRequiredError rather than attempting unauthenticated deletes that would fail. Same guard as list-create.

Source

Thrown at clis/twitter/list-delete.js:98

        { name: 'listId', positional: true, type: 'string', required: true, help: 'Numeric ID of the list you own (e.g. from `opencli twitter lists`)' },
        { name: 'confirm', type: 'boolean', default: false, help: 'Required. Set --confirm true to delete the list.' },
        { name: 'timeout', type: 'int', default: 300, help: 'Max seconds for the overall delete command (default: 300)' },
    ],
    columns: ['listId', 'name', 'members', 'status', 'message'],
    func: async (page, kwargs) => {
        const listId = String(kwargs.listId || '').trim();
        if (!listId || !/^\d+$/.test(listId)) {
            throw new ArgumentError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected numeric ID.`, 'Example: opencli twitter list-delete 123456789 --confirm true');
        }
        if (!normalizeConfirm(kwargs.confirm)) {
            throw new ArgumentError('Refusing to delete list without --confirm true', 'Example: opencli twitter list-delete 123456789 --confirm true');
        }

        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)');

        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 listsBefore = await getManagedLists(page, headers);
        const targetList = listsBefore.find((list) => list.id === listId);
        if (!targetList) {
            throw new CommandExecutionError(`List ${listId} not found among your lists (${listsBefore.length} lists fetched).`);
        }

        await page.goto(`https://x.com/i/lists/${listId}`);
        await page.wait({ selector: '[data-testid="primaryColumn"]' });
        const deleteResult = unwrapBrowserResult(await page.evaluate(`(async () => {
            const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into x.com in the automation browser, then re-run the delete command
  2. Run the CLI's Twitter login/bootstrap step to establish the session
  3. Ensure the same browser profile is reused across runs
  4. Re-login to refresh expired cookies

Example fix

// before
opencli twitter list-delete 123456789 --confirm true
// after
opencli twitter login
opencli twitter list-delete 123456789 --confirm true
Defensive patterns

Strategy: try-catch

Validate before calling

const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some((c) => c.name === 'ct0' && c.value)) {
  throw new Error('x.com session missing — authenticate before deleting lists');
}

Type guard

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

Try / catch

try {
  await runListDelete(page, kwargs);
} catch (e) {
  if (e instanceof AuthRequiredError && /ct0/.test(e.message)) {
    await twitterLogin();
    await runListDelete(page, kwargs);
  } else throw e;
}

Prevention

When it happens

Trigger: After page.goto('https://x.com') and page.wait(3), getCookies({ url: 'https://x.com' }) finds no 'ct0' cookie — no active x.com login in the automation browser.

Common situations: Never logged into x.com in the automation browser; session expired or revoked; cookies cleared between runs; using a different browser profile than the one holding the login.

Related errors


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