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
timeline.js:169 performs the same ct0 CSRF-cookie check as thread.js before issuing authenticated timeline GraphQL requests. It reads cookies for https://x.com via page.getCookies and throws AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)') when none is found, because HomeTimeline/UserTweets endpoints require a logged-in session's CSRF token.
Source
Thrown at clis/twitter/timeline.js:169
name: 'type',
default: 'for-you',
choices: ['for-you', 'following'],
help: 'Which home-timeline feed to read. Default for-you (algorithmic). Use following for the chronological feed of accounts you follow.',
},
{ name: 'limit', type: 'int', default: 20, help: 'Maximum number of tweets to return (default 20).' },
{ name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the 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 X\'s native ordering.' },
],
columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],
func: async (page, kwargs) => {
const limit = kwargs.limit || 20;
const timelineType = kwargs.type === 'following' ? 'following' : 'for-you';
const { endpoint, method, fallbackQueryId } = TIMELINE_ENDPOINTS[timelineType];
// Cookie context auto-established by framework pre-nav (Strategy.COOKIE + domain).
// Read CSRF token directly from the cookie store via CDP — zero page.evaluate round-trip.
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)');
// Dynamically resolve queryId for the selected endpoint
const queryId = await resolveTwitterQueryId(page, endpoint, fallbackQueryId);
// Build auth headers
const headers = JSON.stringify({
Authorization: `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
'X-Csrf-Token': ct0,
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
});
// Paginate — fetch in browser, parse in TypeScript
const allTweets = [];
const seen = new Set();
let cursor = null;
// Runaway guard only; --limit and cursor exhaustion control normal pagination.
for (let i = 0; i < MAX_PAGINATION_PAGES && allTweets.length < limit; i++) {
const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
const variables = buildTimelineVariables(timelineType, fetchCount, cursor);
const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);View on GitHub (pinned to 49907e53dc)
Solutions
- Log into x.com in the browser profile the CLI uses, then rerun the command
- Check that a ct0 cookie exists for https://x.com (DevTools > Application > Cookies) — its presence is exactly what this guard tests
- If you inject cookies programmatically, make sure you include ct0 (and auth_token) for the x.com domain before the request
- Re-authenticate after any x.com logout event; a single expired session invalidates the CSRF pairing
Defensive patterns
Strategy: validation
Validate before calling
const cookies = await page.getCookies({ url: 'https://x.com' });
const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
if (!ct0) throw new Error('x.com session missing (no ct0 cookie): log in first'); Type guard
function isAuthenticated(cookies) {
return cookies?.some?.((c) => c.name === 'ct0' && !!c.value) ?? false;
} Try / catch
import { AuthRequiredError } from '@jackwener/opencli/errors';
try {
const timeline = await fetchTimeline(type, opts);
} catch (e) {
if (e instanceof AuthRequiredError) {
console.error('Open a visible browser session, log into x.com, then retry.');
process.exitCode = 1;
} else throw e;
} Prevention
- Pre-flight check for ct0 before long timeline pulls
- Keep the CLI browser profile logged in and avoid cookie clears
- After any x.com logout or password change, re-authenticate before batch jobs
- Run batch fetches during low-activity windows with generous page delays
When it happens
Trigger: Running `opencli twitter timeline` or `opencli twitter tweets` while the driven browser profile has no ct0 cookie for x.com — logged-out profile, expired session, cookies cleared, or cookie context targeting the wrong domain.
Common situations: New environment/CI without a logged-in profile; x.com revoked the session (password change, suspicious-activity logout); user wiped browser data; profile switcheroo between twitter.com and x.com cookie domains; long-lived sessions that x.com rotates periodically.
Related errors
- 12306 tk auth cookie missing
- Not logged into x.com (no ct0 cookie)
- Not logged into x.com (no ct0 cookie)
- csrftoken cookie missing - make sure you are logged in to In
- csrftoken cookie missing - make sure you are logged in to In
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7667150b3afab02e.
Report an issue: GitHub.