{"record":{"id":"fd58da0d0266ddc8","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-fd58da","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/download.js","lineNumber":351,"sourceCode":"            if (!username) {\n                throw new ArgumentError('twitter download username must be a valid Twitter/X handle', 'Example: opencli twitter download @jack --limit 20');\n            }\n            return downloadUserMedia(page, username, limit, output);\n        }\n        catch (err) {\n            if (err instanceof CliError) throw err;\n            throw new CommandExecutionError(`twitter download failed: ${err?.message ?? String(err)}`);\n        }\n    },\n});\n\nasync function downloadUserMedia(page, username, limit, output) {\n    await page.goto(`https://x.com/${username}`);\n    await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\n\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 userMediaOperation = await resolveTwitterOperationMetadata(page, 'UserMedia', USER_MEDIA_OPERATION);\n    const userByScreenNameOperation = await resolveTwitterOperationMetadata(page, 'UserByScreenName', USER_BY_SCREEN_NAME_OPERATION);\n\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\n    const ubsUrl = buildUserByScreenNameUrl(userByScreenNameOperation, username);\n    const userLookup = requireFetchPayload(await page.evaluate(`async () => {\n      try {\n        const resp = await fetch(\"${ubsUrl}\", { headers: ${headers}, credentials: 'include' });\n        if (!resp.ok) return { ok: false, status: resp.status };\n        const payload = await resp.json();\n        return { ok: true, payload };","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/download.js#L333-L369","documentation":"The UserMedia GraphQL flow authenticates with the browser's ct0 cookie as the X-CSRF-Token header. Before querying, the code reads cookies for https://x.com and throws AuthRequiredError if no ct0 cookie exists, because x.com only issues ct0 to logged-in sessions and GraphQL calls would fail without it.","triggerScenarios":"Running `opencli twitter download <username>` with a browser profile that has never logged into x.com, after logging out, or with cookies cleared/expired so no ct0 cookie is present.","commonSituations":"Fresh automation browser profile without a login; session expired (x.com rotates/invalidates ct0); pointing the CLI at the wrong profile directory; corporate proxy stripping cookies.","solutions":["Log into x.com in the browser profile the CLI uses, then re-run the command","Verify the CLI is pointed at the correct browser profile/user-data-dir containing the logged-in session","Clear cookies and log in again if the session is corrupt or expired"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some((c) => c.name === 'ct0')) {\n  throw new Error('Log into x.com in the browser profile first');\n}","typeGuard":"const hasCt0 = (cookies) => Array.isArray(cookies) && cookies.some((c) => c.name === 'ct0' && c.value);","tryCatchPattern":"try {\n  await downloadUserMedia(page, username, limit);\n} catch (err) {\n  if (err.name === 'AuthRequiredError') {\n    console.error('Open a browser on x.com and log in, then retry.');\n  }\n}","preventionTips":["Log into x.com in the exact browser profile the CLI uses","Re-login when sessions expire; ct0 is rotated on logout/expiry","Do not share profiles across machines where cookies may be cleared"],"tags":["auth","cookies","twitter"],"backgroundTag":"not-authenticated","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}