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
listRemoveUser navigates to x.com and reads the browser cookies looking for the ct0 cookie, which X.com sets on login and uses as the CSRF token for authenticated GraphQL calls. If no ct0 cookie exists it throws AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)') at list-remove-core.js:66, because list mutations are impossible without an authenticated session.
Source
Thrown at clis/twitter/list-remove-core.js:66
}
return { ok: false, error: `HTTP ${status}` };
}
export async function listRemoveUser(page, kwargs) {
const listId = String(kwargs.listId || '').trim();
const username = String(kwargs.username || '').replace(/^@/, '').trim();
if (!listId || !/^\d+$/.test(listId)) {
throw new ArgumentError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected numeric ID.`);
}
if (!username) throw new ArgumentError('twitter list-remove username is required');
// Strategy.UI does not get a domain URL pre-nav from the framework.
// This page context is load-bearing for pre-target GraphQL calls below.
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 userByScreenNameQueryId = await resolveTwitterQueryId(page, 'UserByScreenName', USER_BY_SCREEN_NAME_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',
});
const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);
const userId = unwrapBrowserResult(await page.evaluate(`async () => {
const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });
if (!resp.ok) return null;
const d = await resp.json();
return d.data?.user?.result?.rest_id || null;
}`));
if (!userId) throw new CommandExecutionError(`Could not resolve user @${username}`);
View on GitHub (pinned to 49907e53dc)
Solutions
- Log into x.com in the same browser profile the CLI uses, then re-run the command
- Point the CLI at the browser profile where you are already logged in (set the user-data-dir/profile option for the launch)
- Re-authenticate if your session was invalidated (password change, logout-everywhere, security challenge)
- Verify with a read-only command (e.g. listing your lists) that the profile's session works before write operations
- Ensure cookies aren't blocked for x.com in the automation browser (check page.getCookies({ url: 'https://x.com' }) output)
Example fix
// before (fresh profile, not logged in)
await cliRun('twitter list-remove 1734567890123456789 alice');
// after (launch with the logged-in profile, or pre-auth first)
await launchBrowser({ userDataDir: '/path/to/logged-in-profile' });
await cliRun('twitter list-remove 1734567890123456789 alice'); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the session before a write command:
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) {
throw new Error('x.com session missing — log into x.com in the automation browser profile first');
} Type guard
function hasCt0Cookie(cookies) {
return Array.isArray(cookies) && cookies.some(c => c.name === 'ct0' && !!c.value);
} Try / catch
import { AuthRequiredError } from '@jackwener/opencli/errors';
try {
await run(`opencli twitter list-remove ${listId} ${username}`);
} catch (e) {
if (e instanceof AuthRequiredError || /no ct0 cookie/.test(e.message)) {
// pause and prompt the user to log into x.com in the CLI's browser profile, then retry
await promptXComLogin();
return run(`opencli twitter list-remove ${listId} ${username}`);
}
throw e;
} Prevention
- Log into x.com in the exact browser profile/user-data-dir the CLI launches
- Warm up with a read-only command to validate the session before write operations
- After password changes or logout-everywhere, re-authenticate before running list mutations
- Ensure cookies for x.com are not blocked in the automation browser
- In CI, persist and reuse a logged-in browser profile between runs
When it happens
Trigger: Running the browser command without ever logging into x.com in the automation browser's profile; the session expired or was logged out so cookies were cleared; using a fresh/incognito browser context or a different user-data-dir than the one where login happened; cookies filtered by URL such that the x.com cookie jar is empty (wrong domain, cookie policy blocking cookies); a proxy or region redirect putting the session on a different domain's cookies.
Common situations: CI or headless runs with a brand-new browser profile; logging into x.com in your normal Chrome but the CLI launching its own isolated profile; X logged the profile out after a password change or security challenge; system clock skew invalidating the session; cookie blocking extensions or hardened browser settings.
Related errors
- 12306 tk auth cookie missing
- Boss wt2 / t cookies missing
- Instagram story publish could not derive current user id fro
- x.com
- x.com
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/53c3b9ed61f9db62.
Report an issue: GitHub.