{"record":{"id":"87b7bd0a3de73001","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-87b7bd","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/user-timeline.js","lineNumber":190,"sourceCode":"    if (!username) {\n        await page.goto('https://x.com/home');\n        await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\n        const href = unwrapBrowserResult(await page.evaluate(`() => {\n            const link = document.querySelector('a[data-testid=\"AppTabBar_Profile_Link\"]');\n            return link ? link.getAttribute('href') : null;\n        }`));\n        if (!href || typeof href !== 'string') {\n            throw new AuthRequiredError('x.com', 'Could not detect logged-in user. Are you logged in?');\n        }\n        username = normalizeTwitterScreenName(href);\n        if (!username) {\n            throw new AuthRequiredError('x.com', 'Could not detect logged-in user. Are you logged in?');\n        }\n    }\n\n    const cookies = await page.getCookies({ url: 'https://x.com' });\n    const ct0 = cookies.find((cookie) => cookie.name === 'ct0')?.value || null;\n    if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n\n    const userTweetsOperation = await resolveTwitterOperationMetadata(page, 'UserTweets', USER_TWEETS_OPERATION);\n    const userByScreenNameOperation = await resolveTwitterOperationMetadata(page, 'UserByScreenName', USER_BY_SCREEN_NAME_OPERATION);\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    const userByScreenNameUrl = buildUserByScreenNameUrl(userByScreenNameOperation, username);\n    const userId = unwrapBrowserResult(await page.evaluate(`async () => {\n        const resp = await fetch(${JSON.stringify(userByScreenNameUrl)}, { headers: ${headers}, credentials: 'include' });\n        if (!resp.ok) return null;\n        const data = await resp.json();\n        return data?.data?.user?.result?.rest_id || null;\n    }`));\n    if (!userId) throw new CommandExecutionError(`Could not resolve @${username}`);\n    return { username, userId, headers, userTweetsOperation };","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/user-timeline.js#L172-L208","documentation":"resolveUserTimelineContext drives a logged-in x.com browser session and needs the 'ct0' CSRF cookie that x.com sets for authenticated users. After loading x.com it calls page.getCookies() and throws AuthRequiredError when no ct0 cookie is present, because GraphQL calls (UserTweets, UserByScreenName) require the X-Csrf-Token header derived from it. Without ct0 the requests would be rejected as unauthenticated, so the tool fails fast.","triggerScenarios":"Calling the user-timeline command/`context` flow when the Puppeteer/Playwright page used by resolveUserTimelineContext is not logged into x.com: fresh browser profile with no session, expired session cookies, logged-out/incognito context, or x.com failing to set the ct0 cookie on the loaded page.","commonSituations":"Running the CLI for the first time without ever logging into x.com in the automation profile; x.com logged the session out server-side (password change, security event); using a data dir that was wiped; x.com A/B changes altering cookie names; running against a page that landed on a login or error page instead of x.com.","solutions":["Log into x.com in the browser session/profile the tool uses (complete the login flow manually in that browser, then retry).","Persist the browser profile (user-data-dir / storageState) so cookies survive between runs.","Verify the page actually reaches https://x.com before getCookies is called; if x.com redirects to a login page, re-authenticate.","Re-run after clearing stale state if x.com invalidated the session; if cookie names changed upstream, update the cookie lookup."],"exampleFix":"// before: reusing a throwaway context\nconst page = await browser.newPage();\nawait runTimeline(page, 'someuser');\n// after: reuse a persistent, logged-in profile\nconst ctx = await browser.createBrowserContext({\n  userDataDir: '/home/me/.opencli/x-profile' // already logged into x.com\n});\nconst page = await ctx.newPage();\nawait runTimeline(page, 'someuser');","handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some((c) => c.name === 'ct0')) {\n  throw new Error('Run the login flow for x.com before fetching the timeline.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetchUserTimeline(page, username);\n} catch (err) {\n  if (err instanceof AuthRequiredError) {\n    await runXLoginFlow(page); // open x.com/login, wait for user\n    return fetchUserTimeline(page, username);\n  }\n  throw err;\n}","preventionTips":["Persist the browser profile (user-data-dir/storageState) so the x.com session survives restarts.","Check for the ct0 cookie at startup and trigger the login flow proactively.","Re-login after password changes or x.com security signouts."],"tags":["authentication","cookies","browser-automation","csrf"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}