jackwener/OpenCLI · warning · EmptyResultError

@ has no recent tweets

Error message

@${username} has no recent tweets

What it means

EmptyResultError (code EMPTY_RESULT) is thrown by `twitter tweets` after pagination loops over the user timeline and collects zero tweets. The library throws it so scripts get a distinct exit code instead of an empty table, with a hint that the account may be private or suspended. If the account is accessible, it means the scraper parsed no tweet nodes from any fetched page.

Solutions

  1. Log in to X in the browser session so private/follow-gated timelines are visible, then retry
  2. Verify the account exists and is public (open https://x.com/<username> manually); skip suspended/private accounts
  3. If the account should have tweets, suspect a parser/X markup change — check for library updates or report the issue
  4. Retry later if rate-limited (empty payloads can be a soft-block symptom)

Example fix

// before
await cli.run(['twitter', 'tweets', 'someprivateuser']);
// after
try {
  await cli.run(['twitter', 'tweets', 'someprivateuser']);
} catch (e) {
  if (e.code === 'EMPTY_RESULT') console.error('Account private/suspended or no tweets');
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling
class EmptyResultError extends Error {}
const isPrivateOrSuspended = (username) =>
  fetch(`https://x.com/${username}`).then(r => r.status !== 200);

Type guard

function isEmptyResultError(e) { return e && e.code === 'EMPTY_RESULT'; }

Try / catch

try {
  const tweets = await getTweets(username);
} catch (e) {
  if (e.code === 'EMPTY_RESULT') {
    // account private, suspended, or no tweets — skip or warn
    console.warn(`@${username}: no recent tweets (private/suspended?)`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `twitter tweets <username>` when every parseUserTweets pass over the timeline API returns an empty tweets array, so `all.length === 0` after the pagination loop breaks (no nextCursor or cursor stalls).

Common situations: Target account is protected/private or suspended (logged-out or unauthenticated scraping sees nothing); account has genuinely no tweets; X changed its timeline markup so the parser matches zero nodes; heavy rate-limiting returns empty payloads.

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/97739c717bb39607. Report an issue: GitHub.

Appendix: source

Thrown at clis/twitter/tweets.js:145

        const all = [];
        let cursor = null;
        // Runaway guard only; --limit and cursor exhaustion control normal pagination.
        for (let i = 0; i < MAX_USER_TWEETS_PAGES && all.length < limit; i++) {
            if (i > 0 && pageDelaySeconds > 0) {
                await page.wait(pageDelaySeconds);
            }
            const fetchCount = Math.min(USER_TWEETS_PAGE_SIZE, limit - all.length + 10);
            const data = await fetchUserTimelinePage(page, context, cursor, fetchCount);
            if (data?.error) {
                if (all.length === 0) throw new CommandExecutionError(describeTwitterApiError('UserTweets', data.error));
                break;
            }
            const { tweets, nextCursor } = parseUserTweets(data, seen);
            all.push(...tweets);
            if (!nextCursor || nextCursor === cursor) break;
            cursor = nextCursor;
        }
        if (all.length === 0) throw new EmptyResultError(`@${username} has no recent tweets`, 'Account may be private or suspended');
        return applyTopByEngagement(all.slice(0, limit), kwargs['top-by-engagement']);
    },
});

export const __test__ = {
    MAX_TWEETS_LIMIT,
    sanitizeQueryId,
    buildUserTweetsUrl,
    buildUserByScreenNameUrl,
    extractTweet,
    parseUserTweets,
    normalizeLimit,
    normalizePageDelaySeconds,
};

View on GitHub (pinned to 49907e53dc)