{"record":{"id":"42b09b96873b7acd","repo":"jackwener/OpenCLI","slug":"x-com","errorCode":null,"errorMessage":"x.com","messagePattern":"x\\.com","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/twitter/following.js","lineNumber":154,"sourceCode":"        },\n        { name: 'limit', type: 'int', default: 50, help: 'Maximum number of following rows to return (default 50). Must be a positive integer.' },\n    ],\n    columns: ['screen_name', 'name', 'bio', 'followers'],\n    func: async (page, kwargs) => {\n        const limit = kwargs.limit === undefined || kwargs.limit === null ? 50 : Number(kwargs.limit);\n        if (!Number.isInteger(limit) || limit <= 0) {\n            throw new ArgumentError('twitter following --limit must be a positive integer', 'Example: opencli twitter following @elonmusk --limit 200');\n        }\n        const rawUser = String(kwargs.user ?? '').trim();\n        let targetUser = normalizeScreenName(rawUser);\n        if (rawUser && !targetUser) {\n            throw new ArgumentError('twitter following user must be a valid Twitter/X handle', 'Example: opencli twitter following @elonmusk --limit 200');\n        }\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)\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n\n        if (!targetUser) {\n            // Force a navigation to the home surface so the AppTabBar sidebar\n            // is rendered; the framework pre-nav lands on bare x.com which\n            // does not always expose AppTabBar_Profile_Link.\n            await page.goto('https://x.com/home');\n            await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\n            // Bridge wraps primitive page.evaluate returns as { session, data:<value> };\n            // unwrap so the href string is usable downstream.\n            // NOTE: the function-literal form `() => ...` silently drops\n            // primitive return values through the bridge — only the template\n            // string form preserves the `data` field.\n            const href = unwrapBrowserResult(await page.evaluate(`() => {\n        const link = document.querySelector('a[data-testid=\"AppTabBar_Profile_Link\"]');\n        return link ? link.getAttribute('href') : null;\n      }`));\n            if (!href || typeof href !== 'string')\n                throw new AuthRequiredError('x.com', 'Could not detect logged-in user. Are you logged in?');","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/following.js#L136-L172","documentation":"AuthRequiredError with domain 'x.com' and message 'Not logged into x.com (no ct0 cookie)'. The twitter following command reads the browser's cookies for https://x.com and requires the ct0 cookie (X's CSRF token, only issued to authenticated sessions). Its absence means the shared browser profile has no active x.com login, so the authenticated GraphQL Followers endpoints cannot be called.","triggerScenarios":"page.getCookies({url:'https://x.com'}) returns no cookie named 'ct0' — the automation browser was never logged into x.com, the session was logged out, or cookies were cleared/expired.","commonSituations":"Fresh automation profile with no prior manual login; X forced a logout (password change, suspicious-activity signout); cookie purge by browser housekeeping or a privacy tool; session expired after long inactivity.","solutions":["Log into x.com once in the automation browser profile so ct0 (and auth_token) cookies are issued.","Re-run the command after confirming x.com shows you logged in in that profile.","If X keeps logging you out, re-authenticate and avoid sharing the profile with tools that clear cookies.","Persist a dedicated browser profile for opencli so the session survives restarts."],"exampleFix":"// before\n# browser profile not logged in -> no ct0\nopencli twitter following @elonmusk\n// after\n# log into x.com in the automation browser first, then:\nopencli twitter following @elonmusk --limit 200","handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some(c => c.name === 'ct0')) {\n  throw new Error('Not logged into x.com: log in once in the automation browser profile before running this command');\n}","typeGuard":"const hasSession = (cookies) => Array.isArray(cookies) && cookies.some(c => c.name === 'ct0' && c.value);","tryCatchPattern":"import { AuthRequiredError } from '@jackwener/opencli/errors';\ntry {\n  rows = await opencli.twitter.following(user, { limit });\n} catch (e) {\n  if (e instanceof AuthRequiredError && /no ct0 cookie/.test(e.message)) {\n    await launchInteractiveLogin('https://x.com'); // one-time manual login, then retry\n    rows = await opencli.twitter.following(user, { limit });\n  } else throw e;\n}","preventionTips":["Log into x.com once in the dedicated automation browser profile","Persist that profile so cookies (ct0, auth_token) survive restarts","Avoid cookie-clearing privacy tools on the automation profile","Re-authenticate promptly when X force-logs the session out"],"tags":["authentication","cookies","browser-automation","twitter"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}