{"record":{"id":"ff12c233df01319a","repo":"jackwener/OpenCLI","slug":"could-not-resolve-user-username","errorCode":null,"errorMessage":"Could not resolve user @${username}","messagePattern":"Could not resolve user @(.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-add-core.js","lineNumber":141,"sourceCode":"        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        // opencli >=1.7.x wraps page.evaluate return values as { session, data }.\n        // Unwrap before use so JSON.stringify of nested values doesn't become \"[object Object]\".\n        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);\n        const userIdRaw = await page.evaluate(`async () => {\n            const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });\n            if (!resp.ok) return null;\n            const d = await resp.json();\n            return d.data?.user?.result?.rest_id || null;\n        }`);\n        const userId = unwrapBrowserResult(userIdRaw);\n        if (!userId) {\n            throw new CommandExecutionError(`Could not resolve user @${username}`);\n        }\n\n        // ListsManagementPageTimeline — used for list existence check + before/after member_count.\n        const listsQueryId = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID);\n        const listsUrl = `/i/api/graphql/${listsQueryId}/ListsManagementPageTimeline?features=${encodeURIComponent(JSON.stringify(LISTS_MANAGEMENT_FEATURES))}`;\n        const listsDataRaw = await page.evaluate(`async () => {\n            const r = await fetch(${JSON.stringify(listsUrl)}, { headers: ${headers}, credentials: 'include' });\n            if (!r.ok) return { __error: 'HTTP ' + r.status };\n            return await r.json();\n        }`);\n        // Don't unwrap listsData: opencli spreads GraphQL response to top-level + adds session;\n        // parseListsManagement reads `.data.viewer.*` from this shape directly.\n        const listsData = listsDataRaw;\n        const parsedLists = listsData && !listsData.__error\n            ? parseListsManagement(listsData, new Set())\n            : [];\n        if (listsData && listsData.__error) {\n            throw new CommandExecutionError(`Could not fetch lists: ${listsData.__error}`);","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-add-core.js#L123-L159","documentation":"After obtaining the ct0 CSRF cookie, listAddUser resolves the target handle to a numeric user ID via the UserByScreenName GraphQL endpoint. The in-page fetch either returned a non-ok response or a body without data.user.result.rest_id, so no rest_id could be extracted; without the ID the ListAddMember mutation cannot be issued, so CommandExecutionError is thrown.","triggerScenarios":"Calling list-add with a @username that does not exist, was renamed, or is misspelled; the UserByScreenName fetch returns non-ok (rate limit, auth expired mid-run, stale queryId) and the evaluate returns null; the account is suspended/protected so rest_id is absent from the response.","commonSituations":"Typo in the handle; user deleted/renamed their account since the input list was written; X rate-limits the GraphQL endpoint after a batch; X rotated the UserByScreenName queryId and the fallback constant is stale so the API rejects the request; account logged out mid-batch.","solutions":["Check the username spelling and confirm the account exists by visiting https://x.com/<username> in a browser.","Re-run after a pause if rate limited (HTTP 429) — wait a few minutes before retrying.","Confirm you are still logged in (ct0 cookie present); re-authenticate if the session expired.","Update the UserByScreenName queryId fallback in list-add-core.js if X rotated it (resolveTwitterQueryId's live lookup failed)."],"exampleFix":"// before\nopencli twitter list-add 123456789 @alise\nCommandExecutionError: Could not resolve user @alise\n// after\nopencli twitter list-add 123456789 @alice","handlingStrategy":"validation","validationCode":"const HANDLE_RE = /^[A-Za-z0-9_]{1,15}$/;\nif (!HANDLE_RE.test(username.replace(/^@/, ''))) {\n  throw new Error(`@${username} is not a valid handle; verify it exists on x.com before running`);\n}","typeGuard":"function isResolvableUsername(username) {\n  return typeof username === 'string' && /^[A-Za-z0-9_]{1,15}$/.test(username.replace(/^@/, ''));\n}","tryCatchPattern":"try {\n  await listAddUser(page, { listId, username });\n} catch (e) {\n  if (/Could not resolve user @/.test(e.message)) {\n    console.error(`Verify https://x.com/${username} exists; skipping user.`);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Verify each handle resolves (open x.com/<handle>) before batch runs.","Strip @ prefixes and URLs; pass bare handles only.","Back off on 429s — rate limits also surface as unresolvable users here.","Keep opencli updated so UserByScreenName queryId fallbacks stay current."],"tags":["twitter","graphql","user-lookup","input-validation"],"backgroundTag":"user-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}