jackwener/OpenCLI · error · CommandExecutionError

describeTwitterApiError('ListLatestTweetsTimeline', data.err

Error message

describeTwitterApiError('ListLatestTweetsTimeline', data.error, 'list may be private')

What it means

While paginating the list timeline, the ListLatestTweetsTimeline GraphQL endpoint returned an error payload. If this happened on the first page (allTweets empty), the library surfaces it via describeTwitterApiError with the hint 'list may be private'. Subsequent-page errors just break pagination and return the tweets collected so far.

Source

Thrown at clis/twitter/list-tweets.js:187

            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
            'X-Csrf-Token': ct0,
            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });
        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(100, limit - allTweets.length + 10);
            const apiUrl = buildUrl(queryId, listId, fetchCount, cursor);
            const data = throwIfLoginWall(await page.evaluate(`async () => {
                ${BROWSER_JSON_SNIFF_FN}
                return await fetchJsonOrLoginWall(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });
            }`), { url: apiUrl });
            if (data?.error) {
                if (allTweets.length === 0)
                    throw new CommandExecutionError(describeTwitterApiError('ListLatestTweetsTimeline', data.error, 'list may be private'));
                break;
            }
            const { tweets, nextCursor } = parseListTimeline(data, seen);
            allTweets.push(...tweets);
            if (!nextCursor || nextCursor === cursor || tweets.length === 0)
                break;
            cursor = nextCursor;
        }
        const trimmed = allTweets.slice(0, limit);
        return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the concrete status/detail embedded by describeTwitterApiError in the message (e.g. 404 → wrong id; 401/403 → private or logged out)
  2. Re-login to x.com in the controlled browser if the error is auth-flavored
  3. Verify the list id via `opencli twitter lists` and that your account can see it on x.com
  4. Slow down / retry later on 429; update the library on 400 (queryId expired)

Example fix

// before
const tweets = await run('twitter', 'list-tweets', { listId }); // throws on first-page API error
// after
try {
  const tweets = await run('twitter', 'list-tweets', { listId });
} catch (e) {
  if (e.message.includes('list may be private')) {
    console.error(`No access to list ${listId}: check it exists and your account can view it.`);
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const lists = await run('twitter', 'lists', { limit: 200 });
if (!lists.some(l => String(l.id) === String(listId))) {
  throw new Error(`List ${listId} not visible to your account (deleted, private, or wrong id).`);
}

Type guard

const apiErrored = (d) => d && typeof d === 'object' && 'error' in d;

Try / catch

try {
  const tweets = await run('twitter', 'list-tweets', { listId });
} catch (e) {
  if (/list may be private/.test(e.message)) {
    console.error(`Cannot read list ${listId}: verify it exists and your account has access.`);
  } else if (/429/.test(e.message)) {
    await sleep(60000); return run('twitter', 'list-tweets', { listId });
  } else throw e;
}

Prevention

When it happens

Trigger: First fetch of the ListLatestTweetsTimeline endpoint returned data.error — e.g. HTTP 401/403 (no access to a private list), 404 (list deleted or wrong id), 429 (rate limit), or 400 (stale queryId) — propagated by fetchJsonOrLoginWall through throwIfLoginWall.

Common situations: Reading a list owned by someone else that is private; list id typo → 404; heavy scraping tripping X rate limits; twitter-openapi queryId outdated after X rotates GraphQL ids; session cookies half-expired so the API treats the call as logged out.

Related errors


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