jackwener/OpenCLI · error · CommandExecutionError

describeTwitterApiError('ListsManagementPageTimeline', data.

Error message

describeTwitterApiError('ListsManagementPageTimeline', data.error)

What it means

The ListsManagementPageTimeline GraphQL call used to enumerate your lists returned an error payload ({error: status} from a non-OK fetch). The library converts it via describeTwitterApiError into a CommandExecutionError naming the GraphQL operation and status.

Source

Thrown at clis/twitter/lists.js:166

                    } catch {}
                }
            } catch {}
            return null;
        }`);
        const queryId = unwrap(queryIdRaw) || LISTS_QUERY_ID;
        const headers = JSON.stringify({
            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
            'X-Csrf-Token': ct0,
            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });
        const apiUrl = buildUrl(queryId);
        const data = await page.evaluate(`async () => {
            const r = await fetch(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });
            return r.ok ? await r.json() : { error: r.status };
        }`);
        if (data?.error) {
            throw new CommandExecutionError(describeTwitterApiError('ListsManagementPageTimeline', data.error));
        }
        const seen = new Set();
        if (!getListsManagementInstructions(data)) {
            throw new CommandExecutionError('Twitter lists returned an unexpected payload shape');
        }
        const lists = parseListsManagement(data, seen);
        if (lists.length === 0) {
            throw new EmptyResultError('twitter lists', 'No owned or subscribed lists found');
        }
        return lists.slice(0, limit);
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the status code in the thrown message; 400 → update the library so a fresh queryId is fetched from twitter-openapi
  2. Re-login to x.com if the status is 401/403
  3. Back off and retry on 429; reduce polling frequency
  4. Retry later or check X API status on 5xx

Example fix

// before
const lists = await run('twitter', 'lists', { limit: 50 }); // throws on API error
// after
try {
  const lists = await run('twitter', 'lists', { limit: 50 });
} catch (e) {
  if (/400/.test(e.message)) console.error('queryId likely expired — update opencli/twitter-openapi.');
  else if (/429/.test(e.message)) { await sleep(60000); return run('twitter', 'lists', { limit: 50 }); }
  else throw e;
}
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('Log into x.com first.');

Type guard

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

Try / catch

try {
  const lists = await run('twitter', 'lists', { limit: 50 });
} catch (e) {
  const m = /HTTP|\b(\d{3})\b/.exec(e.message);
  if (m && m[1] === '429') { await new Promise(r => setTimeout(r, 60000)); return run('twitter', 'lists', { limit: 50 }); }
  if (m && m[1] === '400') { console.error('queryId expired — update opencli/twitter-openapi.'); }
  throw e;
}

Prevention

When it happens

Trigger: The fetch of the lists-management API URL returned a non-OK status — 401/403 (session invalid), 400 (expired twitter-openapi queryId), 404 (endpoint removed), or 429 (rate limited) — and page.evaluate mapped it to {error: r.status}.

Common situations: Stale queryId fetched from the fa0311/twitter-openapi placeholder after X rotates GraphQL ids (HTTP 400); expired session cookies; rate limiting after repeated list commands; transient X API incidents.

Related errors


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