jackwener/OpenCLI · error · CommandExecutionError
Could not resolve user @${username}
Error message
Could not resolve user @${username} What it means
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.
Source
Thrown at clis/twitter/list-add-core.js:141
const headers = JSON.stringify({
'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
'X-Csrf-Token': ct0,
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
});
// opencli >=1.7.x wraps page.evaluate return values as { session, data }.
// Unwrap before use so JSON.stringify of nested values doesn't become "[object Object]".
const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);
const userIdRaw = await page.evaluate(`async () => {
const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });
if (!resp.ok) return null;
const d = await resp.json();
return d.data?.user?.result?.rest_id || null;
}`);
const userId = unwrapBrowserResult(userIdRaw);
if (!userId) {
throw new CommandExecutionError(`Could not resolve user @${username}`);
}
// ListsManagementPageTimeline — used for list existence check + before/after member_count.
const listsQueryId = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID);
const listsUrl = `/i/api/graphql/${listsQueryId}/ListsManagementPageTimeline?features=${encodeURIComponent(JSON.stringify(LISTS_MANAGEMENT_FEATURES))}`;
const listsDataRaw = await page.evaluate(`async () => {
const r = await fetch(${JSON.stringify(listsUrl)}, { headers: ${headers}, credentials: 'include' });
if (!r.ok) return { __error: 'HTTP ' + r.status };
return await r.json();
}`);
// Don't unwrap listsData: opencli spreads GraphQL response to top-level + adds session;
// parseListsManagement reads `.data.viewer.*` from this shape directly.
const listsData = listsDataRaw;
const parsedLists = listsData && !listsData.__error
? parseListsManagement(listsData, new Set())
: [];
if (listsData && listsData.__error) {
throw new CommandExecutionError(`Could not fetch lists: ${listsData.__error}`);View on GitHub (pinned to 49907e53dc)
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).
Example fix
// before opencli twitter list-add 123456789 @alise CommandExecutionError: Could not resolve user @alise // after opencli twitter list-add 123456789 @alice
Defensive patterns
Strategy: validation
Validate before calling
const HANDLE_RE = /^[A-Za-z0-9_]{1,15}$/;
if (!HANDLE_RE.test(username.replace(/^@/, ''))) {
throw new Error(`@${username} is not a valid handle; verify it exists on x.com before running`);
} Type guard
function isResolvableUsername(username) {
return typeof username === 'string' && /^[A-Za-z0-9_]{1,15}$/.test(username.replace(/^@/, ''));
} Try / catch
try {
await listAddUser(page, { listId, username });
} catch (e) {
if (/Could not resolve user @/.test(e.message)) {
console.error(`Verify https://x.com/${username} exists; skipping user.`);
return null;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.
Related errors
- List ${listId} not found among your lists (${parsedLists.len
- Could not resolve user @${username}
- ${probe.detail}
- twitter_collection_request_error
- device_follow
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ff12c233df01319a.
Report an issue: GitHub.