jackwener/OpenCLI · error · CommandExecutionError

SPA navigation to notifications failed. Twitter may have cha

Error message

SPA navigation to notifications failed. Twitter may have changed its routing.

What it means

CommandExecutionError thrown when SPA navigation to https://x.com/notifications did not land on the /notifications path. The CLI triggers a client-side route change via history.pushState + popstate, waits for capture, then reads window.location.pathname; if it differs from '/notifications' the routing change failed, likely because Twitter changed its SPA routing or an interstitial intercepted navigation.

Source

Thrown at clis/twitter/notifications.js:30

        { name: 'limit', type: 'int', default: 20, help: 'Maximum number of notifications to return (default 20).' },
    ],
    columns: ['id', 'action', 'author', 'text', 'url'],
    func: async (page, kwargs) => {
        // 1. Navigate to home first (we need a loaded Twitter page for SPA navigation)
        await page.goto('https://x.com/home');
        await page.wait(3);
        // 2. Install interceptor BEFORE SPA navigation
        await page.installInterceptor('NotificationsTimeline');
        // 3. SPA navigate to notifications via history API
        await page.evaluate(`() => {
        window.history.pushState({}, '', '/notifications');
        window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
    }`);
        await page.waitForCapture(5);
        // Verify SPA navigation succeeded
        const currentUrl = await page.evaluate('() => window.location.pathname');
        if (currentUrl !== '/notifications') {
            throw new CommandExecutionError('SPA navigation to notifications failed. Twitter may have changed its routing.');
        }
        // 4. Scroll to trigger pagination
        await page.autoScroll({ times: 2, delayMs: 2000 });
        // 5. Retrieve data
        const requests = await page.getInterceptedRequests();
        if (!requests || requests.length === 0)
            return [];
        let results = [];
        const seen = new Set();
        for (const req of requests) {
            try {
                // GraphQL response: { data: { viewer: ... } } (one level of .data)
                let instructions = [];
                if (req.data?.viewer?.timeline_response?.timeline?.instructions) {
                    instructions = req.data.viewer.timeline_response.timeline.instructions;
                }
                else if (req.data?.viewer_v2?.user_results?.result?.notification_timeline?.timeline?.instructions) {
                    instructions = req.data.viewer_v2.user_results.result.notification_timeline.timeline.instructions;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Ensure the browser session is authenticated so /notifications does not redirect to /login.
  2. Log the actual currentUrl to see where navigation landed and adapt the expected path.
  3. Update the opencli twitter CLI if X renamed its notification route (check for updates/issues).
  4. As a fallback, navigate directly with page.goto('https://x.com/notifications') and re-run, then report persistent routing failures.

Example fix

// before
if (currentUrl !== '/notifications') {
    throw new CommandExecutionError('SPA navigation to notifications failed. Twitter may have changed its routing.');
}
// after
if (currentUrl !== '/notifications') {
    await page.goto('https://x.com/notifications');
    const fallbackUrl = await page.evaluate('() => window.location.pathname');
    if (!fallbackUrl.startsWith('/notifications')) {
        throw new CommandExecutionError(`Notifications navigation failed, landed on: ${fallbackUrl}`);
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const path_ = await page.evaluate('() => window.location.pathname');
if (!path_.startsWith('/notifications')) {
  throw new Error(`Not on notifications (at ${path_}); check login state and route`);
}

Try / catch

try {
  const notifs = await twitterNotifications();
} catch (err) {
  if (err.message.includes('SPA navigation to notifications failed')) {
    console.error('Check that the session is logged in and X routing is unchanged; try direct page.goto fallback');
  } else throw err;
}

Prevention

When it happens

Trigger: After dispatching the synthetic popstate, page.evaluate('() => window.location.pathname') returned something other than '/notifications' — e.g. '/home', '/login', or an error route — because X changed route names, the session is unauthenticated, or the SPA redirected.

Common situations: Twitter/X renaming or restructuring notification routes; logged-out sessions being redirected to /login; regional/variant redirects; the SPA boot failing so the router never applies the pushed state.

Related errors


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