jackwener/OpenCLI · error · EmptyResultError

twitter download @${username}

Error message

twitter download @${username}

What it means

If UserByScreenName returns no GraphQL errors but data.user.result.rest_id is still missing, the code throws EmptyResultError titled `twitter download @<username>` with detail 'Could not resolve @<username>'. This means Twitter's response was structurally valid but contained no user result payload.

Source

Thrown at clis/twitter/download.js:379

    });

    const ubsUrl = buildUserByScreenNameUrl(userByScreenNameOperation, username);
    const userLookup = requireFetchPayload(await page.evaluate(`async () => {
      try {
        const resp = await fetch("${ubsUrl}", { headers: ${headers}, credentials: 'include' });
        if (!resp.ok) return { ok: false, status: resp.status };
        const payload = await resp.json();
        return { ok: true, payload };
      } catch (err) {
        return { ok: false, error: err?.message ?? String(err) };
      }
    }`));
    const normalizedUserLookup = normalizeTwitterGraphqlPayload(userLookup);
    if (Array.isArray(normalizedUserLookup?.errors) && normalizedUserLookup.errors.length > 0) {
        throw new CommandExecutionError(`Twitter UserByScreenName returned GraphQL errors: ${JSON.stringify(normalizedUserLookup.errors).slice(0, 200)}`);
    }
    const userId = normalizedUserLookup?.data?.user?.result?.rest_id;
    if (!userId) throw new EmptyResultError(`twitter download @${username}`, `Could not resolve @${username}`);

    const seen = new Set();
    const all = [];
    let cursor = null;
    let hasMorePages = false;
    for (let i = 0; i < MAX_PAGINATION_PAGES && all.length < limit; i++) {
        const fetchCount = nextUserMediaFetchCount(limit, all.length);
        if (fetchCount === 0) break;
        const url = buildUserMediaUrl(userMediaOperation, userId, fetchCount, cursor);
        const data = normalizeTwitterGraphqlPayload(requireFetchPayload(await page.evaluate(`async () => {
        try {
          const r = await fetch("${url}", { headers: ${headers}, credentials: 'include' });
          if (!r.ok) return { ok: false, status: r.status };
          return { ok: true, payload: await r.json() };
        } catch (err) {
          return { ok: false, error: err?.message ?? String(err) };
        }
        }`)));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Confirm the account is public and viewable at https://x.com/<username>
  2. Update the CLI to match the current UserByScreenName response shape (rest_id path)
  3. Retry later in case of transient server-side truncation
Defensive patterns

Strategy: type-guard

Validate before calling

const userId = normalized?.data?.user?.result?.rest_id;
if (!userId) {
  console.warn('Could not resolve user; verify the account is public');
}

Type guard

const hasResolvedUser = (payload) => typeof payload?.data?.user?.result?.rest_id === 'string';

Try / catch

try {
  await cmd();
} catch (err) {
  if (err.name === 'EmptyResultError' && err.message.includes('Could not resolve')) {
    // treat as 'user unresolvable' — verify account visibility
  }
}

Prevention

When it happens

Trigger: UserByScreenName returns an empty/partial data node: protected or deleted account edge cases, response shape changes after x.com updates, or responses where user.result lacks rest_id (e.g. some restricted states).

Common situations: x.com changed the GraphQL response shape so rest_id moved or was renamed; the account exists but its result is withheld in your region; caching/stale operation metadata producing a truncated response.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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