jackwener/OpenCLI · error · CommandExecutionError

Could not fetch lists: ${listsData.__error}

Error message

Could not fetch lists: ${listsData.__error}

What it means

After resolving the user, listRemoveUser fetches the ListsManagementPageTimeline GraphQL timeline inside the page. Any non-OK HTTP response is converted to { __error: 'HTTP <status>' } and surfaced as this CommandExecutionError. It means the lists-management timeline could not be downloaded, so the target list cannot be located.

Source

Thrown at clis/twitter/list-remove-core.js:94

        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);
        const userId = unwrapBrowserResult(await page.evaluate(`async () => {
            const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });
            if (!resp.ok) return null;
            const d = await resp.json();
            return d.data?.user?.result?.rest_id || null;
        }`));
        if (!userId) throw new CommandExecutionError(`Could not resolve user @${username}`);

        // Resolve listId → name so we can match the dialog row.
        const listsQueryId = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID);
        const listsUrl = `/i/api/graphql/${listsQueryId}/ListsManagementPageTimeline?features=${encodeURIComponent(JSON.stringify(LISTS_MANAGEMENT_FEATURES))}`;
        const listsData = unwrapBrowserResult(await page.evaluate(`async () => {
            const r = await fetch(${JSON.stringify(listsUrl)}, { headers: ${headers}, credentials: 'include' });
            if (!r.ok) return { __error: 'HTTP ' + r.status };
            return await r.json();
        }`));
        if (listsData && listsData.__error) {
            throw new CommandExecutionError(`Could not fetch lists: ${listsData.__error}`);
        }
        const parsedLists = parseListsManagement(listsData, new Set());
        const targetList = parsedLists.find((l) => l.id === listId);
        if (!targetList) {
            throw new CommandExecutionError(`List ${listId} not found among your lists.`);
        }
        const targetName = targetList.name;

        await page.goto(`https://x.com/${username}`);
        await page.wait({ selector: '[data-testid="primaryColumn"]' });
        const uiResult = unwrapBrowserResult(await page.evaluate(`(async () => {
            const sleep = (ms) => new Promise(r => setTimeout(r, ms));
            const findOne = (sel, root = document) => root.querySelector(sel);
            const waitFor = async (fn, { timeoutMs = 8000, intervalMs = 200 } = {}) => {
                const t0 = Date.now();
                while (Date.now() - t0 < timeoutMs) { const v = fn(); if (v) return v; await sleep(intervalMs); }
                return null;
            };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-login to x.com in the controlled browser to refresh the auth session, then retry.
  2. Check the HTTP status embedded in the message: 401/403 → session problem, 429 → back off and retry later, 400 → stale queryId/features.
  3. Update LISTS_MANAGEMENT_QUERY_ID and LISTS_MANAGEMENT_FEATURES to match the current x.com web app payloads.
  4. Retry after a delay if the status is 429 or 5xx (rate limiting or Twitter-side outage).
  5. Confirm the account is logged in and has not been flagged for automation (some restrictions return 403 on GraphQL endpoints).

Example fix

// before (stale hardcoded query id → HTTP 400)
const LISTS_MANAGEMENT_QUERY_ID = '78UbkyXwXBD98IgUWXOy9g';
// after
const LISTS_MANAGEMENT_QUERY_ID = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID_FALLBACK); // resolved live from page
Defensive patterns

Strategy: retry

Validate before calling

const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some(c => c.name === 'ct0')) throw new Error('Login to x.com before running list-remove');

Type guard

function listsFetchedOk(d) {
  return typeof d === 'object' && d !== null && d.__error === undefined;
}

Try / catch

try {
  await listRemoveUser(page, { listId, username });
} catch (e) {
  if (/Could not fetch lists: HTTP (429|5\d\d)/.test(e.message)) {
    await sleep(60_000); // back off, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: The in-page fetch of /i/api/graphql/<queryId>/ListsManagementPageTimeline returns a non-2xx status — typically 401/403 from an expired or missing ct0/auth session, 429 rate limiting, 400 from stale/rotated queryId or features object, or 5xx from Twitter.

Common situations: Session cookies expired mid-run; Twitter rotated the ListsManagementPageTimeline query ID so the hardcoded one 400s; the LISTS_MANAGEMENT_FEATURES blob no longer satisfies Twitter's feature-flag validation; aggressive automation triggered 429s; transient Twitter 5xx.

Related errors


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