{"record":{"id":"7667150b3afab02e","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-766715","errorCode":null,"errorMessage":"Not logged into x.com (no ct0 cookie)","messagePattern":"Not logged into x\\.com \\(no ct0 cookie\\)","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/twitter/timeline.js","lineNumber":169,"sourceCode":"            name: 'type',\n            default: 'for-you',\n            choices: ['for-you', 'following'],\n            help: 'Which home-timeline feed to read. Default for-you (algorithmic). Use following for the chronological feed of accounts you follow.',\n        },\n        { name: 'limit', type: 'int', default: 20, help: 'Maximum number of tweets to return (default 20).' },\n        { 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.' },\n    ],\n    columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],\n    func: async (page, kwargs) => {\n        const limit = kwargs.limit || 20;\n        const timelineType = kwargs.type === 'following' ? 'following' : 'for-you';\n        const { endpoint, method, fallbackQueryId } = TIMELINE_ENDPOINTS[timelineType];\n        // Cookie context auto-established by framework pre-nav (Strategy.COOKIE + domain).\n        // Read CSRF token directly from the cookie store via CDP — zero page.evaluate round-trip.\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0)\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        // Dynamically resolve queryId for the selected endpoint\n        const queryId = await resolveTwitterQueryId(page, endpoint, fallbackQueryId);\n        // Build auth headers\n        const headers = JSON.stringify({\n            Authorization: `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,\n            'X-Csrf-Token': ct0,\n            'X-Twitter-Auth-Type': 'OAuth2Session',\n            'X-Twitter-Active-User': 'yes',\n        });\n        // Paginate — fetch in browser, parse in TypeScript\n        const allTweets = [];\n        const seen = new Set();\n        let cursor = null;\n        // Runaway guard only; --limit and cursor exhaustion control normal pagination.\n        for (let i = 0; i < MAX_PAGINATION_PAGES && allTweets.length < limit; i++) {\n            const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering\n            const variables = buildTimelineVariables(timelineType, fetchCount, cursor);\n            const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/timeline.js#L151-L187","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nconst ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\nif (!ct0) throw new Error('x.com session missing (no ct0 cookie): log in first');","typeGuard":"function isAuthenticated(cookies) {\n  return cookies?.some?.((c) => c.name === 'ct0' && !!c.value) ?? false;\n}","tryCatchPattern":"import { AuthRequiredError } from '@jackwener/opencli/errors';\ntry {\n  const timeline = await fetchTimeline(type, opts);\n} catch (e) {\n  if (e instanceof AuthRequiredError) {\n    console.error('Open a visible browser session, log into x.com, then retry.');\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["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"],"tags":["authentication","cookies","csrf","twitter"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}