{"record":{"id":"5558c14b71ccd8ce","repo":"jackwener/OpenCLI","slug":"invalid-twitter-x-username-json-stringify-part","errorCode":null,"errorMessage":"Invalid Twitter/X username: ${JSON.stringify(part.trim())}","messagePattern":"Invalid Twitter/X username: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/follow-batch.js","lineNumber":21,"sourceCode":"import { unwrapBrowserResult } from './shared.js';\n\nconst USERNAME_RE = /^[A-Za-z0-9_]{1,15}$/;\nconst DEFAULT_DELAY_MS = 3000;\nconst MAX_DELAY_MS = 60000;\n\nexport function parseBatchUsernames(input) {\n    const raw = String(input || '').trim();\n    if (!raw) {\n        throw new ArgumentError('At least one Twitter/X username is required');\n    }\n\n    const usernames = [];\n    const seen = new Set();\n    for (const part of raw.split(',')) {\n        const username = part.trim().replace(/^@+/, '');\n        if (!username) continue;\n        if (!USERNAME_RE.test(username)) {\n            throw new ArgumentError(`Invalid Twitter/X username: ${JSON.stringify(part.trim())}`);\n        }\n        const key = username.toLowerCase();\n        if (seen.has(key)) continue;\n        seen.add(key);\n        usernames.push(username);\n    }\n\n    if (!usernames.length) {\n        throw new ArgumentError('At least one Twitter/X username is required');\n    }\n    return usernames;\n}\n\nasync function readFollowState(page, username) {\n    return unwrapBrowserResult(await page.evaluate(`(async () => {\n        try {\n            let attempts = 0;\n            while (attempts < 20) {","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/follow-batch.js#L3-L39","documentation":"parseBatchUsernames validates each comma-separated token against USERNAME_RE after stripping leading '@' characters and throws ArgumentError for any token that does not match. Twitter/X screen names allow only alphanumerics and underscores, so spaces, hyphens, dots, slashes, or URLs fail validation. The JSON.stringify of the raw token in the message identifies the offending part.","triggerScenarios":"Passing a full profile URL (https://x.com/alice) instead of the screen name; tokens with hyphens/dots; stray quotes or punctuation around names; tokens with embedded spaces or invisible characters (CRLF) that break the regex.","commonSituations":"Copy-pasting profile URLs into the username list; piping usernames from another tool with trailing punctuation; Windows line endings adding hidden \\r characters; typos like 'bo b'.","solutions":["Replace each item with the bare screen name (letters, digits, underscore only): '@alice,@bob_99', not 'https://x.com/alice'.","Strip URLs/prefixes before invoking, e.g. sed 's#https://x.com/##g'.","Check the input source for invisible characters (CRLF, quotes); trim and normalize line endings.","Fix only the token named in the message — tokens that reduce to empty after '@'-stripping are skipped, not errors."],"exampleFix":"// before\nopencli twitter follow-batch 'https://x.com/alice,@bob-1'\n// after\nopencli twitter follow-batch 'alice,bob_1'","handlingStrategy":"validation","validationCode":"const USERNAME_RE = /^[A-Za-z0-9_]{1,15}$/;\nfunction sanitizeUsernames(input) {\n  return String(input ?? '')\n    .split(',')\n    .map((p) => p.trim().replace(/^@+/, ''))\n    .filter((u) => u && !USERNAME_RE.test(u) ? (console.error(`Skipping invalid: ${u}`), false) : Boolean(u));\n}\n// Pre-strip URLs: input.replaceAll(/https?:\\/\\/x\\.com\\//g, '')","typeGuard":"function isValidUsername(u) {\n  return typeof u === 'string' && /^[A-Za-z0-9_]{1,15}$/.test(u);\n}","tryCatchPattern":"try {\n  await followBatch(rawUsernames);\n} catch (e) {\n  if (e.code === 'ARGUMENT' && e.message.startsWith('Invalid Twitter/X username')) {\n    const bad = e.message.match(/\"(.*)\"/)?.[1];\n    console.error(`Remove or fix the invalid token: ${bad}`);\n  } else throw e;\n}","preventionTips":["Strip profile URLs to screen names before passing them in.","Normalize input: trim, remove quotes, fix CRLF line endings from Windows sources.","Pre-validate each token with /^[A-Za-z0-9_]{1,15}$/ in your pipeline.","Keep names bare — no '@' needed (stripped anyway), no hyphens/dots (not allowed on X)."],"tags":["argument-validation","twitter","input-validation","regex"],"backgroundTag":"input-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}