{"record":{"id":"5629143ac457a77e","repo":"jackwener/OpenCLI","slug":"twitter-followers-user-must-be-a-valid-twitter-x-h","errorCode":null,"errorMessage":"twitter followers user must be a valid Twitter/X handle","messagePattern":"twitter followers user must be a valid Twitter/X handle","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/followers.js","lineNumber":102,"sourceCode":"            required: false,\n            help: 'Twitter/X handle (with or without @). Omit to fetch followers of the currently logged-in account.',\n        },\n        { name: 'limit', type: 'int', default: 50, help: 'Maximum number of follower rows to return (default 50). Must be a positive integer.' },\n    ],\n    // Preserve the historical three-column contract even though the GraphQL\n    // payload also contains per-user relationship counts. Use `twitter profile`\n    // when a dedicated follower count is needed.\n    columns: ['screen_name', 'name', 'bio'],\n    func: async (page, kwargs) => {\n        const limit = kwargs.limit;\n        if (!Number.isInteger(limit) || limit <= 0) {\n            throw new ArgumentError('limit must be a positive integer');\n        }\n\n        const rawUser = String(kwargs.user ?? '').trim();\n        let targetUser = normalizeScreenName(rawUser);\n        if (rawUser && !targetUser) {\n            throw new ArgumentError('twitter followers user must be a valid Twitter/X handle', 'Example: opencli twitter followers @elonmusk --limit 100');\n        }\n        await page.goto('https://x.com/home');\n        await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\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        if (!targetUser) {\n            // Bridge wraps primitive page.evaluate returns as { session, data:<value> };\n            // unwrap so the href string is usable downstream.\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 find logged-in user profile link. Are you logged in?');","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/followers.js#L84-L120","documentation":"The library normalizes the user argument with normalizeTwitterScreenName; if a non-empty user string was given but normalization produced nothing, the value is not a valid Twitter/X handle. This guards against typos, embedded URLs, or values with illegal characters being sent to the API. It throws immediately as an ArgumentError with an example in the hint.","triggerScenarios":"Passing user values like 'https://x.com/elonmusk', '@@double@', 'invalid name!', a full profile URL with query params, or any string with characters outside the allowed handle alphabet after trimming.","commonSituations":"Pasting a full profile URL instead of the handle; including trailing slashes or query strings; copying '@' plus whitespace; programmatic callers passing a display name instead of the handle.","solutions":["Pass only the handle, with or without a single leading @, e.g. @elonmusk or elonmusk","Strip URL parts first: const handle = url.split('/').pop().split('?')[0]","Validate with /^@?[A-Za-z0-9_]{1,15}$/ before calling"],"exampleFix":"// before\nopencli twitter followers \"https://x.com/elonmusk\"\n// after\nopencli twitter followers @elonmusk --limit 100","handlingStrategy":"validation","validationCode":"const HANDLE_RE = /^@?[A-Za-z0-9_]{1,15}$/;\nif (!HANDLE_RE.test(rawUser)) throw new Error('not a valid X handle: ' + rawUser);","typeGuard":"const isValidHandle = (s) => typeof s === 'string' && /^@?[A-Za-z0-9_]{1,15}$/.test(s.trim());","tryCatchPattern":"try {\n  await opencli.twitter.followers(user);\n} catch (err) {\n  if (err.message.includes('must be a valid Twitter/X handle')) {\n    user = extractHandleFromUrl(user); // take last path segment\n  } else throw err;\n}","preventionTips":["Pass bare handles, never profile URLs","Trim whitespace and strip a single leading @ before calling","Validate with the handle regex upstream in scripts"],"tags":["validation","arguments","twitter-handle"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}