{"record":{"id":"3fff5d47a51c6b5f","repo":"jackwener/OpenCLI","slug":"invalid-listid-json-stringify-kwargs-listid-3fff5d","errorCode":null,"errorMessage":"Invalid listId: ${JSON.stringify(kwargs.listId)}. Expected numeric ID.","messagePattern":"Invalid listId: (.+?)\\. Expected numeric ID\\.","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-remove-core.js","lineNumber":56,"sourceCode":"    longform_notetweets_inline_media_enabled: true,\n    responsive_web_grok_image_annotation_enabled: true,\n    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        });","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L38-L74","documentation":"listRemoveUser validates that kwargs.listId is a non-empty string of digits before doing anything. Anything else (undefined, empty, an @handle, a list URL/slug, or a numeric JS value stringified oddly) triggers this ArgumentError from clis/twitter/list-remove-core.js:56. It's a fail-fast input contract: X list mutations need the numeric list REST id, e.g. '1234567890123456789'.","triggerScenarios":"Calling the list-remove command/function with: no listId argument; listId='' or whitespace; listId as a list slug like 'my-list'; a full URL like 'https://x.com/i/lists/123456' pasted whole; a listId with non-digit characters (letters, commas, '@'); listId passed as a number that lost precision or was formatted with separators like '1,234,567'.","commonSituations":"Copy-pasting the list page URL instead of the numeric id; supplying the list's @slug from a share link; shell quoting stripping the value so kwargs.listId is empty; mixing up listId with the owner's user id; scripting the CLI and passing null/undefined when a lookup step upstream returned nothing.","solutions":["Pass the numeric list id, e.g. `opencli twitter list-remove 1734567890123456789 someuser`","Get the correct id from `opencli twitter lists` (the listId column)","Strip a pasted URL down to the digit-only segment, or add a pre-parse that extracts /i/lists/(\\d+)","Quote the argument in your shell so it isn't dropped or mangled","If calling listRemoveUser programmatically, coerce with String(value).trim() and pre-test /^\\d+$/ before invoking"],"exampleFix":"// before\nopencli twitter list-remove https://x.com/i/lists/1734567890123456789 alice\n// after\nopencli twitter list-remove 1734567890123456789 alice","handlingStrategy":"validation","validationCode":"function normalizeListId(raw) {\n  const m = String(raw ?? '').match(/(\\d+)\\/?$/); // tolerate URLs like /i/lists/123456\n  const id = m ? m[1] : String(raw ?? '').replace(/[^\\d]/g, '');\n  if (!/^\\d+$/.test(id)) throw new Error(`listId must be numeric, got: ${JSON.stringify(raw)}`);\n  return id;\n}\n// call: await removeUser(normalizeListId(inputUrlOrId), username);","typeGuard":"function isValidListId(v) {\n  return typeof v === 'string' || typeof v === 'number';\n}\nfunction isNumericListId(v) {\n  return isValidListId(v) && /^\\d+$/.test(String(v).trim());\n}","tryCatchPattern":"try {\n  await run(`opencli twitter list-remove ${listId} ${username}`);\n} catch (e) {\n  if (e instanceof ArgumentError && /Invalid listId/.test(e.message)) {\n    // recover: fetch valid ids and retry with the right one\n    const lists = await run('opencli twitter lists');\n    const match = lists.find(l => l.name === expectedName);\n    if (match) return run(`opencli twitter list-remove ${match.listId} ${username}`);\n  }\n  throw e;\n}","preventionTips":["Always source listId from `opencli twitter lists`, never from a pasted URL or slug","Validate with /^\\d+$/ before invoking","Quote arguments in shell to avoid empty/mangled values","Don't confuse listId with the owner's user id or the list slug"],"tags":["argument-validation","input-format","twitter","lists"],"backgroundTag":"invalid-argument-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}