{"record":{"id":"5534223b751dfe3f","repo":"jackwener/OpenCLI","slug":"not-logged-into-x-com-no-ct0-cookie-553422","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-tweets.js","lineNumber":138,"sourceCode":"    domain: 'x.com',\n    strategy: Strategy.COOKIE,\n    browser: true,\n    args: [\n        { name: 'listId', positional: true, type: 'string', required: true, help: 'Numeric ID of a Twitter/X list (e.g. from `opencli twitter lists`)' },\n        { name: 'limit', type: 'int', default: 50 },\n        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the list timeline by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps the list\\'s native (recency) ordering.' },\n    ],\n    columns: ['id', 'author', 'bio', 'text', 'likes', 'retweets', 'replies', 'created_at', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],\n    func: async (page, kwargs) => {\n        const listId = String(kwargs.listId || '').trim();\n        if (!listId || !/^\\d+$/.test(listId)) {\n            throw new CommandExecutionError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected a numeric ID (see \\`opencli twitter lists\\`).`);\n        }\n        const limit = kwargs.limit || 50;\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        // opencli >=1.7.x wraps primitive page.evaluate returns as { session, data: <value> }.\n        // Without unwrap, the string queryId becomes \"[object Object]\" when interpolated into the URL,\n        // causing HTTP 400 \"queryId may have expired\".\n        const unwrap = (v) => (v && typeof v === 'object' && 'session' in v && 'data' in v ? v.data : v);\n        const queryIdRaw = await page.evaluate(`async () => {\n            try {\n                const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');\n                if (ghResp.ok) {\n                    const data = await ghResp.json();\n                    const entry = data['${OPERATION_NAME}'];\n                    if (entry && entry.queryId) return entry.queryId;\n                }\n            } catch {}\n            try {\n                const scripts = performance.getEntriesByType('resource')\n                    .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))\n                    .map(r => r.name);\n                for (const scriptUrl of scripts.slice(0, 15)) {","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-tweets.js#L120-L156","documentation":"list-tweets needs the ct0 cookie (X's CSRF token) from the logged-in browser session to sign GraphQL API requests with credentials:'include'. page.getCookies({url:'https://x.com'}) returned no cookie named ct0, so the library throws AuthRequiredError before making any API call.","triggerScenarios":"Calling `twitter list-tweets` while the controlled browser/profile is logged out of x.com, cookies were just cleared, or the page context has no x.com cookies (fresh profile, wrong profile, get.Cookies scoped to a different URL).","commonSituations":"Session expired (X logs sessions out after inactivity); running headless with a profile that was never logged in; cookie jar wiped by a cleanup job; using a profile where login happened on twitter.com vs x.com domain mismatch.","solutions":["Open x.com in the controlled browser and log in, then re-run the command","Verify cookies exist: await page.getCookies({url:'https://x.com'}) and check for ct0 and auth_token","Point the tool at the correct browser profile (the one where you logged in)","If sessions keep dropping, reduce cookie clearing and keep the profile persistent; re-login when auth_token is absent"],"exampleFix":"// before\nawait run('twitter', 'list-tweets', { listId }); // throws AuthRequiredError\n// after\nconst 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 controlled browser first (missing ct0).');\n}\nawait run('twitter', 'list-tweets', { listId });","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 — run login in the controlled browser first.');\n}","typeGuard":"const hasCt0 = (cookies) => Array.isArray(cookies) && cookies.some(c => c.name === 'ct0' && c.value);","tryCatchPattern":"try {\n  await run('twitter', 'list-tweets', { listId });\n} catch (e) {\n  if (e.name === 'AuthRequiredError' || /no ct0 cookie/.test(e.message)) {\n    console.error('Session expired. Re-login to x.com in the automation browser, then retry.');\n  } else throw e;\n}","preventionTips":["Use a persistent browser profile that stays logged in","Pre-flight ct0/auth_token cookie checks before batches of commands","Avoid clearing cookies between runs","Re-login proactively when you see auth errors — X sessions expire"],"tags":["auth","cookies","csrf"],"backgroundTag":"missing-auth-cookie","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}