{"record":{"id":"17bb3812c0009694","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-17bb38","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-add-core.js","lineNumber":119,"sourceCode":"    };\n}\n\nexport async function listAddUser(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.`, 'Example: opencli twitter list-add 123456789 alice');\n        }\n        if (!username) {\n            throw new ArgumentError('twitter list-add username is required', 'Example: opencli twitter list-add 123456789 alice');\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\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        // opencli >=1.7.x wraps page.evaluate return values as { session, data }.\n        // Unwrap before use so JSON.stringify of nested values doesn't become \"[object Object]\".\n        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);\n        const userIdRaw = 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;","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-add-core.js#L101-L137","documentation":"listAddUser needs an authenticated X.com browser session: it navigates to https://x.com and reads the `ct0` cookie, which X sets on login and uses as the CSRF token for all internal GraphQL calls (sent as X-Csrf-Token). If no ct0 cookie exists, the session is not logged in and every subsequent GraphQL call would fail with 401/403, so the command throws AuthRequiredError up front instead of making doomed API calls.","triggerScenarios":"Running `opencli twitter list-add` when the browser profile used by the CLI has no logged-in x.com session: cookies for https://x.com contain no cookie named `ct0` after page.goto('https://x.com') and page.getCookies({url:'https://x.com'}).","commonSituations":"The CLI's browser profile was never logged into X (fresh container/headless profile); cookies expired or were cleared; X logged the account out server-side (suspicious activity, password change); wrong browser profile is configured; running in CI where the interactive login step was skipped.","solutions":["Open the CLI's browser profile and log into x.com manually once (complete 2FA if prompted), then re-run the command.","Verify you are pointing at the intended browser/profile config (persistent user-data-dir with saved session), not a disposable headless context.","Clear stale x.com cookies and log in again if the session was invalidated server-side.","If automating, pre-seed auth_token and ct0 cookies for .x.com into the browser context before running the command."],"exampleFix":"// before (headless run with no session)\n$ opencli twitter list-add 123456789 alice\nError: Not logged into x.com (no ct0 cookie)\n\n// after: log in with the same profile the CLI uses, then\n$ opencli twitter list-add 123456789 alice\nAdded @alice to list 123456789 (verified via member_count ...)","handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nconst loggedIn = cookies.some((c) => c.name === 'ct0' && c.value);\nif (!loggedIn) throw new Error('Log into x.com in the CLI browser profile before running list commands');","typeGuard":"function hasCt0(cookies) {\n  return Array.isArray(cookies) && cookies.some((c) => c && c.name === 'ct0' && typeof c.value === 'string' && c.value.length > 0);\n}","tryCatchPattern":"try {\n  await listAddUser(page, { listId, username });\n} catch (e) {\n  if (e instanceof AuthRequiredError || /no ct0 cookie/.test(e.message)) {\n    console.error('X session missing: open the browser profile and log into x.com, then retry.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Use a persistent browser profile (user-data-dir) so the x.com session survives restarts.","Log into x.com once manually in that exact profile before scripting.","Check for the ct0 cookie as a preflight in CI before running batch jobs.","Re-login after password changes or X security logouts."],"tags":["auth","cookies","csrf","twitter"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}