jackwener/OpenCLI · error · CommandExecutionError

${describeTwitterApiError('TweetDetail', data.error)}

Error message

${describeTwitterApiError('TweetDetail', data.error)}

What it means

thread.js:151 wraps the TweetDetail GraphQL response with throwIfLoginWall and then checks data.error. If the API returned an error payload and no tweets have been collected yet (allTweets.length === 0), it throws CommandExecutionError with a human-readable description from describeTwitterApiError('TweetDetail', data.error). If some tweets were already fetched, it breaks out of pagination instead of throwing.

Source

Thrown at clis/twitter/thread.js:151

            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });
        // Paginate — fetch in browser, parse in TypeScript
        const allTweets = [];
        const seen = new Set();
        let cursor = null;
        for (let i = 0; i < 5; i++) {
            const apiUrl = buildTweetDetailUrl(tweetId, cursor);
            // Browser-side: fetch + JSON parse with HTML-as-JSON sniffer so a
            // login wall / WAF page surfaces as a structured LoginWallError
            // instead of `SyntaxError: Unexpected token '<'`.
            const data = throwIfLoginWall(await page.evaluate(`async () => {
        ${BROWSER_JSON_SNIFF_FN}
        return await fetchJsonOrLoginWall("${apiUrl}", { headers: ${headers}, credentials: 'include' });
      }`), { url: apiUrl });
            if (data?.error) {
                if (allTweets.length === 0)
                    throw new CommandExecutionError(describeTwitterApiError('TweetDetail', data.error));
                break;
            }
            // TypeScript-side: type-safe parsing + cursor extraction
            const { tweets, nextCursor } = parseTweetDetail(data, seen);
            allTweets.push(...tweets);
            if (!nextCursor || nextCursor === cursor)
                break;
            cursor = nextCursor;
        }
        const trimmed = allTweets.slice(0, kwargs.limit);
        return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the described error in the message (e.g. status 429 vs 403) and address it: wait out rate limits or re-login for auth errors
  2. Retry later or with fewer requests if it is a rate-limit/429 case; the API is throttling the session
  3. Update the hard-coded TWEET_DETAIL_QUERY_ID / FEATURES in thread.js to the current values x.com uses (sniff them from the web app's network tab)
  4. Confirm the tweet still exists and is not protected/deleted — those can produce error payloads on first fetch
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: confirm session still valid with a cheap authenticated endpoint
const cookies = await page.getCookies({ url: 'https://x.com' });
const ct0 = cookies.find((c) => c.name === 'ct0')?.value;
if (!ct0) throw new Error('Login required before calling thread fetch');

Type guard

function isApiErrorPayload(data) {
  return data != null && typeof data === 'object' && 'error' in data;
}

Try / catch

import { CommandExecutionError } from '@jackwener/opencli/errors';
try {
  const tweets = await fetchThread(url);
} catch (e) {
  if (e instanceof CommandExecutionError && /429|rate/i.test(e.message)) {
    await sleep(60_000);
    return fetchThread(url); // retry with backoff
  }
  if (e instanceof CommandExecutionError && /40[134]/.test(e.message)) {
    console.error('Session/queryId issue: re-login or update TWEET_DETAIL_QUERY_ID.');
  } else throw e;
}

Prevention

When it happens

Trigger: The TweetDetail fetch inside page.evaluate returns { error: <status|message> } — typically an HTTP error status from fetchJsonOrLoginWall (401/403 auth rejection, 429 rate limit, 404 stale queryId), or a GraphQL error body — on the first page of results.

Common situations: Rate limiting after heavy scraping; X changed the GraphQL response shape or retired the hard-coded TWEET_DETAIL_QUERY_ID; session/CSRF token rejected mid-run; deleted or protected tweet where the API returns errors instead of an empty timeline; login wall detected on the first fetch.

Related errors


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