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

  1. Check the username spelling and confirm the account exists by visiting https://x.com/<username> in a browser.
  2. Re-run after a pause if rate limited (HTTP 429) — wait a few minutes before retrying.
  3. Confirm you are still logged in (ct0 cookie present); re-authenticate if the session expired.
  4. 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

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


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