jackwener/OpenCLI · error · CommandExecutionError

SPA navigation to Twitter followers failed

Error message

SPA navigation to Twitter followers failed

What it means

After capturing the first Followers response, the command verifies via window.location.pathname that the SPA actually ended on a path ending in '/followers'. If the path is missing, non-string, or a different route, the client-side pushState/popstate navigation to the followers list failed. This is a CommandExecutionError guarding against extracting follower data from the wrong page.

Source

Thrown at clis/twitter/followers.js:146

            throw new ArgumentError('twitter followers user cannot be empty', 'Example: opencli twitter followers @elonmusk --limit 100');
        }

        await page.installInterceptor('/Followers?');
        const targetPath = `/${targetUser}/followers`;
        await page.evaluate(`() => {
            const targetPath = ${JSON.stringify(targetPath)};
            window.history.pushState({}, '', targetPath);
            window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
        }`);
        try {
            await page.waitForCapture(CAPTURE_TIMEOUT_SECONDS);
        }
        catch {
            throw new TimeoutError('twitter followers API capture', CAPTURE_TIMEOUT_SECONDS, 'No Followers response was observed after opening the followers list.');
        }
        const currentPath = unwrapBrowserResult(await page.evaluate('() => window.location.pathname'));
        if (typeof currentPath !== 'string' || !currentPath.toLowerCase().endsWith('/followers')) {
            throw new CommandExecutionError('SPA navigation to Twitter followers failed');
        }

        const allFollowers = [];
        const seen = new Set();
        let cursor = null;
        let lastRawResponse = null;
        let pages = 0;

        const consumeCaptured = async () => {
            const requests = await page.getInterceptedRequests();
            if (!Array.isArray(requests)) {
                throw new CommandExecutionError('Twitter followers interceptor returned malformed responses');
            }
            for (const request of requests) {
                const { data, users, nextCursor } = parseFollowers(request);
                const graphqlError = twitterGraphqlError(data);
                if (graphqlError)
                    throw new CommandExecutionError(graphqlError);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-authenticate if the URL shows /login after navigation
  2. Re-run the command — transient SPA failures often clear
  3. Check window.location.pathname manually in the same profile to see where navigation lands
  4. Update the navigation logic if X changed route structure

Example fix

// before
await page.evaluate(`() => { window.history.pushState({}, '', targetPath); window.dispatchEvent(new PopStateEvent('popstate', { state: {} })); }`);
// after (direct navigation fallback)
if (!currentPath.endsWith('/followers')) await page.goto('https://x.com/' + targetUser + '/followers');
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

const pathIsFollowers = (p) => typeof p === 'string' && p.toLowerCase().endsWith('/followers');

Try / catch

try {
  const rows = await opencli.twitter.followers(user, { limit });
} catch (err) {
  if (err.message.includes('SPA navigation')) {
    // fall back to direct navigation variant or retry once after re-auth
    await ensureXLogin();
    return opencli.twitter.followers(user, { limit });
  } else throw err;
}

Prevention

When it happens

Trigger: The popstate-triggered navigation was redirected (e.g. to /login or /home), X intercepted the route change and routed elsewhere, the browser bridge returned a non-string from page.evaluate, or the history API push was blocked.

Common situations: Auth wall redirecting to /login; X routing experimental layouts to different paths; a service worker or client router canceling the navigation; browser bridge unwrapping issues producing non-string pathname.

Related errors


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