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
listAddUser needs an authenticated X.com browser session: it navigates to https://x.com and reads the `ct0` cookie, which X sets on login and uses as the CSRF token for all internal GraphQL calls (sent as X-Csrf-Token). If no ct0 cookie exists, the session is not logged in and every subsequent GraphQL call would fail with 401/403, so the command throws AuthRequiredError up front instead of making doomed API calls.
Source
Thrown at clis/twitter/list-add-core.js:119
};
}
export async function listAddUser(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.`, 'Example: opencli twitter list-add 123456789 alice');
}
if (!username) {
throw new ArgumentError('twitter list-add username is required', 'Example: opencli twitter list-add 123456789 alice');
}
// 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',
});
// opencli >=1.7.x wraps page.evaluate return values as { session, data }.
// Unwrap before use so JSON.stringify of nested values doesn't become "[object Object]".
const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);
const userIdRaw = 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;View on GitHub (pinned to 49907e53dc)
Solutions
- Open the CLI's browser profile and log into x.com manually once (complete 2FA if prompted), then re-run the command.
- Verify you are pointing at the intended browser/profile config (persistent user-data-dir with saved session), not a disposable headless context.
- Clear stale x.com cookies and log in again if the session was invalidated server-side.
- If automating, pre-seed auth_token and ct0 cookies for .x.com into the browser context before running the command.
Example fix
// before (headless run with no session) $ opencli twitter list-add 123456789 alice Error: Not logged into x.com (no ct0 cookie) // after: log in with the same profile the CLI uses, then $ opencli twitter list-add 123456789 alice Added @alice to list 123456789 (verified via member_count ...)
Defensive patterns
Strategy: validation
Validate before calling
const cookies = await page.getCookies({ url: 'https://x.com' });
const loggedIn = cookies.some((c) => c.name === 'ct0' && c.value);
if (!loggedIn) throw new Error('Log into x.com in the CLI browser profile before running list commands'); Type guard
function hasCt0(cookies) {
return Array.isArray(cookies) && cookies.some((c) => c && c.name === 'ct0' && typeof c.value === 'string' && c.value.length > 0);
} Try / catch
try {
await listAddUser(page, { listId, username });
} catch (e) {
if (e instanceof AuthRequiredError || /no ct0 cookie/.test(e.message)) {
console.error('X session missing: open the browser profile and log into x.com, then retry.');
return;
}
throw e;
} Prevention
- Use a persistent browser profile (user-data-dir) so the x.com session survives restarts.
- Log into x.com once manually in that exact profile before scripting.
- Check for the ct0 cookie as a preflight in CI before running batch jobs.
- Re-login after password changes or X security logouts.
When it happens
Trigger: Running `opencli twitter list-add` when the browser profile used by the CLI has no logged-in x.com session: cookies for https://x.com contain no cookie named `ct0` after page.goto('https://x.com') and page.getCookies({url:'https://x.com'}).
Common situations: The CLI's browser profile was never logged into X (fresh container/headless profile); cookies expired or were cleared; X logged the account out server-side (suspicious activity, password change); wrong browser profile is configured; running in CI where the interactive login step was skipped.
Related errors
- Not logged into x.com (no ct0 cookie)
- 12306 tk auth cookie missing
- Bilibili ${label} API requires login or permission: ${messag
- Pinterest did not set a csrftoken cookie for this page
- Not logged into x.com (no ct0 cookie)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/17bb3812c0009694.
Report an issue: GitHub.