jackwener/OpenCLI · error · CommandExecutionError

${describeTwitterApiError(endpoint, data.error)}

Error message

${describeTwitterApiError(endpoint, data.error)}

What it means

timeline.js:194 fetches a timeline page inside page.evaluate and converts non-OK responses to { error: r.status }. If data.error is present and no tweets were collected yet (allTweets.length === 0), it throws CommandExecutionError described by describeTwitterApiError(endpoint, data.error); otherwise pagination just stops with what was gathered.

Source

Thrown at clis/twitter/timeline.js:194

            '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;
        // Runaway guard only; --limit and cursor exhaustion control normal pagination.
        for (let i = 0; i < MAX_PAGINATION_PAGES && allTweets.length < limit; i++) {
            const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
            const variables = buildTimelineVariables(timelineType, fetchCount, cursor);
            const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);
            const data = await page.evaluate(`async () => {
        const r = await fetch("${apiUrl}", { method: "${method}", headers: ${headers}, credentials: 'include' });
        return r.ok ? await r.json() : { error: r.status };
      }`);
            if (data?.error) {
                if (allTweets.length === 0)
                    throw new CommandExecutionError(describeTwitterApiError(endpoint, data.error));
                break;
            }
            const { tweets, nextCursor } = parseHomeTimeline(data, seen);
            allTweets.push(...tweets);
            if (!nextCursor || nextCursor === cursor)
                break;
            cursor = nextCursor;
        }
        const trimmed = allTweets.slice(0, limit);
        return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);
    },
});
export const __test__ = {
    buildTimelineVariables,
    buildHomeTimelineUrl,
    parseHomeTimeline,
};

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the status code in the message: 429 → back off and slow down requests; 401/403 → re-login to refresh ct0/session
  2. Reduce request frequency and add page delays between timeline pages
  3. Update the endpoint's queryId (fallbackQueryId in TIMELINE_ENDPOINTS) to the current value from x.com's web app network traffic
  4. If errors appear only after some pages, rely on the partial results — the command breaks instead of throwing once tweets exist
Defensive patterns

Strategy: retry

Validate before calling

// Ensure session before paginating
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some((c) => c.name === 'ct0' && c.value)) throw new Error('Login required');
// Validate the flag combo up front
if (!Number.isInteger(limit) || limit < 1) throw new Error('limit must be a positive integer');

Type guard

function hasTimelineData(data) {
  return data != null && typeof data === 'object' && !('error' in data)
    && Array.isArray(data?.data?.home?.home_timeline_urt?.instructions ?? []);
}

Try / catch

import { CommandExecutionError } from '@jackwener/opencli/errors';
try {
  const tweets = await fetchTimeline('home', { limit: 100 });
} catch (e) {
  if (e instanceof CommandExecutionError && /429/.test(e.message)) {
    await sleep(5 * 60_000); // back off on rate limit, then retry
  } else if (e instanceof CommandExecutionError) {
    console.error(`Timeline API failed (${e.message}); check session or queryId.`);
  } else throw e;
}

Prevention

When it happens

Trigger: The timeline fetch returns a non-OK HTTP status (401/403 session rejected, 429 rate limited, 404 queryId retired) or a GraphQL error body on the first page of the selected TIMELINE_ENDPOINTS entry.

Common situations: Aggressive polling tripping x.com rate limits; stale/rotated queryId for the endpoint; expired auth mid-run; X changing response schema so parsing fails upstream and error shapes surface; protected/private accounts in UserTweets-like endpoints.

Related errors


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