{"record":{"id":"3d2b7590f67b03b1","repo":"jackwener/OpenCLI","slug":"twitter-following-user-must-be-a-valid-twitter-x-h","errorCode":null,"errorMessage":"twitter following user must be a valid Twitter/X handle","messagePattern":"twitter following user must be a valid Twitter/X handle","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/following.js","lineNumber":148,"sourceCode":"        {\n            name: 'user',\n            positional: true,\n            type: 'string',\n            required: false,\n            help: 'Twitter/X handle (with or without @). Omit to fetch the accounts the currently logged-in user follows.',\n        },\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.","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/following.js#L130-L166","documentation":"ArgumentError thrown when the positional user argument is supplied but cannot be normalized to a valid Twitter/X screen name via normalizeScreenName. The command only validates when a non-empty raw user string was given; if normalization yields an empty string the argument is rejected with an example usage hint.","triggerScenarios":"Calling opencli twitter following with a user argument containing invalid characters (spaces, non-handle symbols beyond @, over-length names), e.g. 'opencli twitter following \"elon musk\"' or 'opencli twitter following https://x.com/some/path'.","commonSituations":"Passing a full profile URL instead of a handle; including display name instead of @handle; extra shell tokens being concatenated into the positional arg; trailing punctuation from copy-paste (e.g. '@elonmusk,').","solutions":["Pass the bare handle (with or without @), e.g. opencli twitter following @elonmusk --limit 200.","Strip URL prefixes if you have a profile link — use only the screen-name path segment.","Quote the argument properly in your shell so spaces do not corrupt it.","Trim stray punctuation/newlines from the value before invoking."],"exampleFix":"// before\nopencli twitter following https://x.com/elonmusk --limit 200\n// after\nopencli twitter following @elonmusk --limit 200","handlingStrategy":"validation","validationCode":"const isValidHandle = (h) => typeof h === 'string' && /^@?[A-Za-z0-9_]{1,15}$/.test(h.trim());\nif (rawUser && !isValidHandle(rawUser)) {\n  throw new Error(`'${rawUser}' is not a valid Twitter/X handle; pass e.g. @elonmusk`);\n}","typeGuard":"const isValidHandle = (h) => typeof h === 'string' && /^@?[A-Za-z0-9_]{1,15}$/.test(h.trim());","tryCatchPattern":"import { ArgumentError } from '@jackwener/opencli/errors';\ntry {\n  rows = await opencli.twitter.following(rawUser, { limit });\n} catch (e) {\n  if (e instanceof ArgumentError && /valid Twitter\\/X handle/.test(e.message)) {\n    // e.g. caller passed a profile URL — extract the screen-name segment and retry\n    const handle = extractHandleFromInput(rawUser);\n    rows = await opencli.twitter.following(handle, { limit });\n  } else throw e;\n}","preventionTips":["Pass bare handles, never full profile URLs or display names","Quote positional args in the shell to avoid token concatenation","Trim punctuation/newlines from user-supplied handles","Normalize inputs (@-optional) before invoking the CLI"],"tags":["argument-validation","cli","input-validation"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}