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
thread.js:128 requires an authenticated x.com session before calling the TweetDetail GraphQL API. It reads cookies for https://x.com via CDP and looks for the ct0 cookie, which x.com sets as the CSRF token for logged-in sessions. If no ct0 cookie exists, AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)') is thrown because the authenticated GraphQL request would otherwise fail with a 403/404.
Source
Thrown at clis/twitter/thread.js:128
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'tweet-id', positional: true, type: 'string', required: true, help: 'Tweet numeric ID (e.g. 1234567890) or full status URL' },
{ name: 'limit', type: 'int', default: 50 },
{ name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the thread 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 conversation\'s structural ordering.' },
],
columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],
func: async (page, kwargs) => {
let tweetId = kwargs['tweet-id'];
const urlMatch = tweetId.match(/\/status\/(\d+)/);
if (urlMatch)
tweetId = urlMatch[1];
// 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)');
// Build auth headers in TypeScript
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;
for (let i = 0; i < 5; i++) {
const apiUrl = buildTweetDetailUrl(tweetId, cursor);
// Browser-side: fetch + JSON parse with HTML-as-JSON sniffer so a
// login wall / WAF page surfaces as a structured LoginWallError
// instead of `SyntaxError: Unexpected token '<'`.
const data = throwIfLoginWall(await page.evaluate(`async () => {
${BROWSER_JSON_SNIFF_FN}View on GitHub (pinned to 49907e53dc)
Solutions
- Log into x.com in the browser profile the CLI drives (run the CLI once with a visible browser and complete the login), then retry the command
- Verify the ct0 cookie exists: in DevTools on x.com, check Application > Cookies > https://x.com for a ct0 entry
- Ensure the CLI's cookie/auth strategy targets the x.com domain, not twitter.com, and that cookies are being loaded into that context
- Re-export or refresh the saved session/cookies if you import them from another tool, since ct0 rotates with sessions
Example fix
// before (logged-out profile)
opencli twitter thread https://x.com/jack/status/123
// after: login first in the driven browser profile
await page.goto('https://x.com/login'); // complete manual login, then rerun
opencli twitter thread https://x.com/jack/status/123 Defensive patterns
Strategy: try-catch
Validate before calling
const cookies = await page.getCookies({ url: 'https://x.com' });
const hasCt0 = cookies.some((c) => c.name === 'ct0' && c.value);
if (!hasCt0) {
console.error('Not logged into x.com — run the CLI with a visible browser and log in first.');
process.exit(1);
} Type guard
function hasCt0Cookie(cookies) {
return Array.isArray(cookies)
&& cookies.some((c) => c.name === 'ct0' && typeof c.value === 'string' && c.value.length > 0);
} Try / catch
import { AuthRequiredError } from '@jackwener/opencli/errors';
try {
await fetchThread(tweetUrl);
} catch (e) {
if (e instanceof AuthRequiredError) {
console.error(`${e.domain} login required: ${e.message}. Re-authenticate and retry.`);
} else throw e;
} Prevention
- Keep a dedicated, logged-in browser profile for the CLI and re-login when sessions expire
- Check for the ct0 cookie as a pre-flight step in scripts before long batch runs
- Avoid clearing browser data on the profile the CLI uses
- Handle x.com logout events (password changes, security reviews) by re-authenticating promptly
When it happens
Trigger: Running `opencli twitter thread <url>` (or any command reaching thread.js:128) while the browser profile used by the CLI is logged out of x.com, the session expired and cookies were rotated/cleared, cookies exist for twitter.com but not x.com, or the framework pre-nav to x.com was blocked.
Common situations: Fresh CI/machine without a logged-in browser profile; x.com logged the session out (password change, security sweep); user cleared cookies; using the wrong browser profile directory; x.com domain migration leaving stale twitter.com-only cookies.
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/d929f9d63a9c9212.
Report an issue: GitHub.