{"record":{"id":"34351d22f05800f5","repo":"jackwener/OpenCLI","slug":"twitter-list-remove-username-is-required","errorCode":null,"errorMessage":"twitter list-remove username is required","messagePattern":"twitter list-remove username is required","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-remove-core.js","lineNumber":58,"sourceCode":"    responsive_web_enhance_cards_enabled: false,\n};\n\nexport function interpretRemoveResponse(status, json) {\n    if (status === 200 && json && (json.id_str || json.id || json.slug)) return { ok: true };\n    if (json && Array.isArray(json.errors) && json.errors.length > 0) {\n        const err = json.errors[0];\n        return { ok: false, error: `${err.code ? '[' + err.code + '] ' : ''}${err.message || 'Unknown error'}` };\n    }\n    return { ok: false, error: `HTTP ${status}` };\n}\n\nexport async function listRemoveUser(page, kwargs) {\n        const listId = String(kwargs.listId || '').trim();\n        const username = String(kwargs.username || '').replace(/^@/, '').trim();\n        if (!listId || !/^\\d+$/.test(listId)) {\n            throw new ArgumentError(`Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected numeric ID.`);\n        }\n        if (!username) throw new ArgumentError('twitter list-remove username is required');\n\n        // Strategy.UI does not get a domain URL pre-nav from the framework.\n        // This page context is load-bearing for pre-target GraphQL calls below.\n        await page.goto('https://x.com');\n        await page.wait(3);\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n\n        const userByScreenNameQueryId = await resolveTwitterQueryId(page, 'UserByScreenName', USER_BY_SCREEN_NAME_QUERY_ID);\n        const headers = JSON.stringify({\n            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,\n            'X-Csrf-Token': ct0,\n            'X-Twitter-Auth-Type': 'OAuth2Session',\n            'X-Twitter-Active-User': 'yes',\n        });\n\n        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L40-L76","documentation":"After validating listId, listRemoveUser normalizes kwargs.username (strips a leading '@') and requires a non-empty result, otherwise throwing ArgumentError 'twitter list-remove username is required' at list-remove-core.js:58. The username is needed both to resolve the user's numeric id via UserByScreenName and to navigate to their profile for the UI removal flow.","triggerScenarios":"Invoking the list-remove command without the username argument; passing an empty string or only '@'; kwargs.username being undefined/null when called programmatically; a typo where the second positional argument was swallowed by shell quoting; passing the username in an option slot the CLI doesn't recognize so it never reaches kwargs.username.","commonSituations":"Forgetting the second positional arg in scripts; shell variables that expand to empty (USERNAME=''); assuming '@handle' alone is rejected when actually only an empty value is — the failure is passing '' or omitting it; CLI arg parsing differences that drop arguments starting with '@' (some tools treat @file specially).","solutions":["Supply the screen name: `opencli twitter list-remove <numericListId> alice`","Quote the '@' form if needed — '@alice' is accepted (the leading @ is stripped) — but ensure it isn't empty","Check your shell: `opencli ... list-remove \"$LIST_ID\" \"$USER\"` and confirm USER isn't unset/empty (set -u helps)","If calling listRemoveUser directly, pass kwargs = { listId: '123...', username: 'alice' } with both keys present","Run `opencli twitter list-remove --help` to confirm the expected argument order for your version"],"exampleFix":"// before (empty variable)\nopencli twitter list-remove 1734567890123456789 \"$TARGET_USER\"   # TARGET_USER unset -> ''\n// after\n: \"${TARGET_USER:?TARGET_USER must be set}\"\nopencli twitter list-remove 1734567890123456789 \"$TARGET_USER\"","handlingStrategy":"validation","validationCode":"function requireUsername(raw) {\n  const u = String(raw ?? '').replace(/^@/, '').trim();\n  if (!u) throw new Error('username is required for twitter list-remove');\n  return u;\n}\n// call: await removeUser(listId, requireUsername(process.env.TARGET_USER));","typeGuard":"function hasUsername(kwargs) {\n  return kwargs != null && typeof kwargs.username === 'string' && kwargs.username.replace(/^@/, '').trim().length > 0;\n}","tryCatchPattern":"try {\n  await run(`opencli twitter list-remove ${listId} ${username}`);\n} catch (e) {\n  if (e instanceof ArgumentError && /username is required/.test(e.message)) {\n    // fix argument construction, then retry once\n    if (!username) throw new Error('Caller bug: username was empty; resolve it before retrying');\n    return run(`opencli twitter list-remove ${listId} ${username}`);\n  }\n  throw e;\n}","preventionTips":["Check argument count/order with --help before scripting","Use ${VAR:?msg} in shell so empty variables fail early","Remember a bare '@' still fails — the handle text itself is required","When calling listRemoveUser directly, assert both kwargs keys exist first"],"tags":["argument-validation","missing-argument","twitter","lists"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}