{"record":{"id":"eb54e2688ddf323b","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-eb54e2","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/device-follow.js","lineNumber":137,"sourceCode":"\ncli({\n    site: 'twitter',\n    name: 'device-follow',\n    access: 'read',\n    description: 'Read the /i/timeline device-follow notification stream (tweets aggregated under a bell-icon \"new posts from @userA and N others\" notification)',\n    domain: 'x.com',\n    strategy: Strategy.COOKIE,\n    browser: true,\n    args: [\n        { name: 'limit', type: 'int', default: 20, help: `Maximum number of tweets to return (1-${MAX_LIMIT}, default 20)` },\n        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank by weighted engagement and return the top N. Default 0 keeps upstream ordering.' },\n    ],\n    columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],\n    func: async (page, kwargs) => {\n        const limit = parseLimit(kwargs.limit);\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n\n        const apiUrl = buildDeviceFollowUrl(limit);\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 data = await page.evaluate(`async () => {\n        try {\n          const r = await fetch(\"${apiUrl}\", { method: \"GET\", headers: ${headers}, credentials: 'include' });\n          if (!r.ok) return { error: r.status };\n          try {\n            return await r.json();\n          } catch (e) {\n            return { errorKind: 'non_json', detail: String(e && e.message || e) };\n          }\n        } catch (e) {","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/device-follow.js#L119-L155","documentation":"The device-follow resolver scrapes x.com in a browser page and reads the ct0 cookie, which x.com sets for authenticated sessions and which doubles as the CSRF token for the GraphQL API. If no ct0 cookie exists for https://x.com, the session is not logged in, so an AuthRequiredError is thrown before any API call is made.","triggerScenarios":"Running the device-follow command in a browser profile that has never logged into x.com, after the user logged out, after x.com cleared/rotated cookies, or when page.getCookies({url:'https://x.com'}) returns no cookie named 'ct0' (e.g. wrong profile or expired session).","commonSituations":"CI environments or headless browser profiles without a persisted logged-in session; a user recently changed passwords causing session invalidation; pointing the CLI at a fresh/empty browser data directory.","solutions":["Log into x.com in the browser profile the CLI uses, then retry","Confirm the CLI is using the intended browser profile/data directory that contains the x.com session","Clear x.com cookies and log in again if the session is stale","Verify cookies are reachable: page.getCookies({url:'https://x.com'}) should list a ct0 entry"],"exampleFix":"// before (fresh headless profile, not logged in)\ncli twitter device-follow\n// after\ncli login-browser  # or open the managed profile and log into x.com first\ncli twitter device-follow","handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some((c) => c.name === 'ct0')) {\n  throw new Error('Open the managed browser profile and log into x.com first');\n}","typeGuard":"function hasCt0(cookies) {\n  return Array.isArray(cookies) && cookies.some((c) => c && c.name === 'ct0' && !!c.value);\n}","tryCatchPattern":"try {\n  await cli.twitter.deviceFollow();\n} catch (e) {\n  if (e instanceof AuthRequiredError) {\n    console.error('Not logged into x.com — open the CLI browser profile and sign in.');\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Log into x.com once in the persistent browser profile the CLI uses","Check for a ct0 cookie before running authenticated x.com commands","After password changes or cookie clears, re-authenticate before scraping","Avoid fresh/headless profiles without persisted session data for authed calls"],"tags":["auth","cookies","session","browser"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}