{"record":{"id":"53c3b9ed61f9db62","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-53c3b9","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/list-remove-core.js","lineNumber":66,"sourceCode":"    }\n    return { ok: false, error: `HTTP ${status}` };\n}\n\nexport async function listRemoveUser(page, kwargs) {\n        const listId = String(kwargs.listId || '').trim();\n        const username = String(kwargs.username || '').replace(/^@/, '').trim();\n        if (!listId || !/^\\d+$/.test(listId)) {\n            throw new ArgumentError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected numeric ID.`);\n        }\n        if (!username) throw new ArgumentError('twitter list-remove username is required');\n\n        // Strategy.UI does not get a domain URL pre-nav from the framework.\n        // This page context is load-bearing for pre-target GraphQL calls below.\n        await page.goto('https://x.com');\n        await page.wait(3);\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 userByScreenNameQueryId = await resolveTwitterQueryId(page, 'UserByScreenName', USER_BY_SCREEN_NAME_QUERY_ID);\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 userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);\n        const userId = unwrapBrowserResult(await page.evaluate(`async () => {\n            const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });\n            if (!resp.ok) return null;\n            const d = await resp.json();\n            return d.data?.user?.result?.rest_id || null;\n        }`));\n        if (!userId) throw new CommandExecutionError(`Could not resolve user @${username}`);\n","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L48-L84","documentation":"listRemoveUser navigates to x.com and reads the browser cookies looking for the ct0 cookie, which X.com sets on login and uses as the CSRF token for authenticated GraphQL calls. If no ct0 cookie exists it throws AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)') at list-remove-core.js:66, because list mutations are impossible without an authenticated session.","triggerScenarios":"Running the browser command without ever logging into x.com in the automation browser's profile; the session expired or was logged out so cookies were cleared; using a fresh/incognito browser context or a different user-data-dir than the one where login happened; cookies filtered by URL such that the x.com cookie jar is empty (wrong domain, cookie policy blocking cookies); a proxy or region redirect putting the session on a different domain's cookies.","commonSituations":"CI or headless runs with a brand-new browser profile; logging into x.com in your normal Chrome but the CLI launching its own isolated profile; X logged the profile out after a password change or security challenge; system clock skew invalidating the session; cookie blocking extensions or hardened browser settings.","solutions":["Log into x.com in the same browser profile the CLI uses, then re-run the command","Point the CLI at the browser profile where you are already logged in (set the user-data-dir/profile option for the launch)","Re-authenticate if your session was invalidated (password change, logout-everywhere, security challenge)","Verify with a read-only command (e.g. listing your lists) that the profile's session works before write operations","Ensure cookies aren't blocked for x.com in the automation browser (check page.getCookies({ url: 'https://x.com' }) output)"],"exampleFix":"// before (fresh profile, not logged in)\nawait cliRun('twitter list-remove 1734567890123456789 alice');\n// after (launch with the logged-in profile, or pre-auth first)\nawait launchBrowser({ userDataDir: '/path/to/logged-in-profile' });\nawait cliRun('twitter list-remove 1734567890123456789 alice');","handlingStrategy":"try-catch","validationCode":"// Pre-check the session before a write command:\nconst cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some(c => c.name === 'ct0')) {\n  throw new Error('x.com session missing — log into x.com in the automation browser profile first');\n}","typeGuard":"function hasCt0Cookie(cookies) {\n  return Array.isArray(cookies) && cookies.some(c => c.name === 'ct0' && !!c.value);\n}","tryCatchPattern":"import { AuthRequiredError } from '@jackwener/opencli/errors';\ntry {\n  await run(`opencli twitter list-remove ${listId} ${username}`);\n} catch (e) {\n  if (e instanceof AuthRequiredError || /no ct0 cookie/.test(e.message)) {\n    // pause and prompt the user to log into x.com in the CLI's browser profile, then retry\n    await promptXComLogin();\n    return run(`opencli twitter list-remove ${listId} ${username}`);\n  }\n  throw e;\n}","preventionTips":["Log into x.com in the exact browser profile/user-data-dir the CLI launches","Warm up with a read-only command to validate the session before write operations","After password changes or logout-everywhere, re-authenticate before running list mutations","Ensure cookies for x.com are not blocked in the automation browser","In CI, persist and reuse a logged-in browser profile between runs"],"tags":["authentication","cookies","session","twitter"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}