jackwener/OpenCLI · error · CommandExecutionError

Could not resolve user @${username}

Error message

Could not resolve user @${username}

What it means

listRemoveUser first resolves the target screen name to a numeric user ID via the UserByScreenName GraphQL endpoint executed inside the browser page. If the fetch fails (non-OK status), the response shape is unexpected, or rest_id is absent, it returns null and this CommandExecutionError is thrown. It means the library could not map @username to a Twitter user ID, so the removal cannot proceed.

Source

Thrown at clis/twitter/list-remove-core.js:83

        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
        if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');

        const userByScreenNameQueryId = await resolveTwitterQueryId(page, 'UserByScreenName', USER_BY_SCREEN_NAME_QUERY_ID);
        const headers = JSON.stringify({
            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
            'X-Csrf-Token': ct0,
            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });

        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);
        const userId = unwrapBrowserResult(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;
        }`));
        if (!userId) throw new CommandExecutionError(`Could not resolve user @${username}`);

        // Resolve listId → name so we can match the dialog row.
        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 listsData = unwrapBrowserResult(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();
        }`));
        if (listsData && listsData.__error) {
            throw new CommandExecutionError(`Could not fetch lists: ${listsData.__error}`);
        }
        const parsedLists = parseListsManagement(listsData, new Set());
        const targetList = parsedLists.find((l) => l.id === listId);
        if (!targetList) {
            throw new CommandExecutionError(`List ${listId} not found among your lists.`);
        }
        const targetName = targetList.name;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the username is correct and the account still exists by visiting https://x.com/<username> in a browser.
  2. Re-login to x.com in the controlled browser so cookies (ct0, auth_token) are fresh, then retry.
  3. Check whether the request is rate-limited (HTTP 429) and wait before retrying.
  4. Update the UserByScreenName query ID (USER_BY_SCREEN_NAME_QUERY_ID) to the current value from x.com's web app if Twitter rotated it.
  5. Confirm the logged-in session can view the target account (age/region blocks and withheld accounts resolve to null).

Example fix

// before
await cli.run('twitter list-remove', { listId: '12345', username: '@flwer' });
// after
await cli.run('twitter list-remove', { listId: '12345', username: '@flower' }); // corrected handle; account exists
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the handle and session before invoking
const username = '@flower'.replace(/^@/, '').trim();
if (!/^[A-Za-z0-9_]{1,15}$/.test(username)) throw new Error('Invalid Twitter handle');
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) throw new Error('Not logged into x.com');

Type guard

function isResolvedUser(v) {
  return typeof v === 'object' && v !== null && typeof v.rest_id === 'string' && /^\d+$/.test(v.rest_id);
}

Try / catch

try {
  await listRemoveUser(page, { listId, username });
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('Could not resolve user')) {
    // verify handle exists / refresh session, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling twitter list-remove with a username whose UserByScreenName fetch returns a non-OK HTTP status (401/403 auth issues, 404 unknown user, 429 rate limit) or a payload without data.user.result.rest_id (suspended/deactivated account, protected/withheld user, or an API shape change after Twitter rotates the query ID).

Common situations: Typo in the handle; the account was renamed, suspended, or deleted; the ct0 cookie/session is stale so Twitter returns 401/403; hard rate-limiting (429); Twitter changed the UserByScreenName response schema; a hardcoded query ID went stale.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/e5bc0e7da2b4d7aa. Report an issue: GitHub.