{"record":{"id":"c23d8c26ab5de3d3","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-c23d8c","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/follow-batch.js","lineNumber":150,"sourceCode":"    domain: 'x.com',\n    strategy: Strategy.UI,\n    browser: true,\n    args: [\n        { name: 'usernames', type: 'string', positional: true, required: true, help: 'Comma-separated Twitter/X screen names, with or without @' },\n        { name: 'delay-ms', type: 'int', default: DEFAULT_DELAY_MS, help: 'Delay between follow attempts in milliseconds' },\n    ],\n    columns: ['username', 'status', 'message'],\n    func: async (page, kwargs) => {\n        if (!page) {\n            throw new CommandExecutionError('Browser session required for twitter follow-batch');\n        }\n\n        const usernames = parseBatchUsernames(kwargs.usernames);\n        const delayMs = parseDelayMs(kwargs['delay-ms']);\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((cookie) => cookie.name === 'ct0')?.value || null;\n        if (!ct0) {\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        }\n\n        const rows = [];\n        for (const [index, username] of usernames.entries()) {\n            if (index > 0 && delayMs > 0) {\n                await page.wait(delayMs / 1000);\n            }\n            rows.push(await followOne(page, username));\n        }\n        return rows;\n    }\n});\n","sourceCodeStart":132,"sourceCodeEnd":163,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/follow-batch.js#L132-L163","documentation":"The command reads cookies for https://x.com and requires the `ct0` cookie, which Twitter/X sets on login (it is the CSRF token used with authenticated API calls). If no ct0 cookie exists, AuthRequiredError('x.com') is thrown. Without it, the follow POSTs to x.com's API would be rejected, so the command fails fast before attempting any follows.","triggerScenarios":"Running `twitter follow-batch` while the connected browser profile is logged out of x.com; cookies cleared or a fresh/incognito profile in use; page.getCookies({url:'https://x.com'}) returns cookies but none named ct0 (visited x.com without logging in); the X session expired.","commonSituations":"Automating on a headless box where no one ever logged into X; X logged the account out after a security prompt or password change; pointing the CLI at the wrong Chrome profile; wiping cookies between runs.","solutions":["Open the connected Chrome/Chromium browser and log in to https://x.com, then re-run the command.","Confirm the correct browser profile is being used if you have multiple profiles.","If the session expired mid-run, re-login to refresh ct0 and other auth cookies.","Avoid fresh --user-data-dir or cookie-clearing modes that wipe the x.com session."],"exampleFix":"// before\n// headless CI, never logged in -> no ct0\n// after\n// 1) launch the connected browser\n// 2) log in to https://x.com\n// 3) re-run: opencli twitter follow-batch 'alice,bob'","handlingStrategy":"try-catch","validationCode":"// Pre-flight: verify an x.com login cookie exists in the connected session\nasync function isLoggedInToX(page) {\n  const cookies = await page.getCookies({ url: 'https://x.com' });\n  return cookies.some((c) => c.name === 'ct0');\n}\n// if (!(await isLoggedInToX(page))) promptLogin('https://x.com');","typeGuard":"function hasCt0(cookies) {\n  return Array.isArray(cookies) && cookies.some((c) => c && c.name === 'ct0' && !!c.value);\n}","tryCatchPattern":"try {\n  await followBatch(usernames);\n} catch (e) {\n  if (e.code === 'AUTH_REQUIRED' && e.domain === 'x.com') {\n    console.error('Log in to https://x.com in the connected Chrome, then re-run.');\n    await openBrowserAndPromptLogin('https://x.com');\n    return retry(followBatch, usernames);\n  }\n  throw e;\n}","preventionTips":["Log into x.com in the connected browser profile before automating writes.","Pin the automation to a persistent Chrome profile (not incognito/fresh user-data-dir).","Re-check login state periodically; X expires sessions after security prompts.","Don't clear cookies between runs; treat AUTH_REQUIRED as 'log in and retry', not a code bug."],"tags":["auth","twitter","cookies","login-required"],"backgroundTag":"not-logged-in","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}