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-tweets needs the ct0 cookie (X's CSRF token) from the logged-in browser session to sign GraphQL API requests with credentials:'include'. page.getCookies({url:'https://x.com'}) returned no cookie named ct0, so the library throws AuthRequiredError before making any API call.
Source
Thrown at clis/twitter/list-tweets.js:138
domain: 'x.com',
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'listId', positional: true, type: 'string', required: true, help: 'Numeric ID of a Twitter/X list (e.g. from `opencli twitter lists`)' },
{ name: 'limit', type: 'int', default: 50 },
{ name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the list timeline 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 list\'s native (recency) ordering.' },
],
columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'replies', 'created_at', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],
func: async (page, kwargs) => {
const listId = String(kwargs.listId || '').trim();
if (!listId || !/^\d+$/.test(listId)) {
throw new CommandExecutionError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected a numeric ID (see \`opencli twitter lists\`).`);
}
const limit = kwargs.limit || 50;
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)');
// opencli >=1.7.x wraps primitive page.evaluate returns as { session, data: <value> }.
// Without unwrap, the string queryId becomes "[object Object]" when interpolated into the URL,
// causing HTTP 400 "queryId may have expired".
const unwrap = (v) => (v && typeof v === 'object' && 'session' in v && 'data' in v ? v.data : v);
const queryIdRaw = await page.evaluate(`async () => {
try {
const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
if (ghResp.ok) {
const data = await ghResp.json();
const entry = data['${OPERATION_NAME}'];
if (entry && entry.queryId) return entry.queryId;
}
} catch {}
try {
const scripts = performance.getEntriesByType('resource')
.filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
.map(r => r.name);
for (const scriptUrl of scripts.slice(0, 15)) {View on GitHub (pinned to 49907e53dc)
Solutions
- Open x.com in the controlled browser and log in, then re-run the command
- Verify cookies exist: await page.getCookies({url:'https://x.com'}) and check for ct0 and auth_token
- Point the tool at the correct browser profile (the one where you logged in)
- If sessions keep dropping, reduce cookie clearing and keep the profile persistent; re-login when auth_token is absent
Example fix
// before
await run('twitter', 'list-tweets', { listId }); // throws AuthRequiredError
// after
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) {
throw new Error('Log into x.com in the controlled browser first (missing ct0).');
}
await run('twitter', 'list-tweets', { listId }); Defensive patterns
Strategy: validation
Validate before calling
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) {
throw new Error('Not logged into x.com — run login in the controlled browser first.');
} Type guard
const hasCt0 = (cookies) => Array.isArray(cookies) && cookies.some(c => c.name === 'ct0' && c.value);
Try / catch
try {
await run('twitter', 'list-tweets', { listId });
} catch (e) {
if (e.name === 'AuthRequiredError' || /no ct0 cookie/.test(e.message)) {
console.error('Session expired. Re-login to x.com in the automation browser, then retry.');
} else throw e;
} Prevention
- Use a persistent browser profile that stays logged in
- Pre-flight ct0/auth_token cookie checks before batches of commands
- Avoid clearing cookies between runs
- Re-login proactively when you see auth errors — X sessions expire
When it happens
Trigger: Calling `twitter list-tweets` while the controlled browser/profile is logged out of x.com, cookies were just cleared, or the page context has no x.com cookies (fresh profile, wrong profile, get.Cookies scoped to a different URL).
Common situations: Session expired (X logs sessions out after inactivity); running headless with a profile that was never logged in; cookie jar wiped by a cleanup job; using a profile where login happened on twitter.com vs x.com domain mismatch.
Related errors
- 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)
- Not logged into x.com (no ct0 cookie)
- Not logged into x.com (no ct0 cookie)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/5534223b751dfe3f.
Report an issue: GitHub.