{"record":{"id":"bfecf49fdb092bc4","repo":"jackwener/OpenCLI","slug":"spa-navigation-to-notifications-failed-twitter-ma","errorCode":null,"errorMessage":"SPA navigation to notifications failed. Twitter may have changed its routing.","messagePattern":"SPA navigation to notifications failed\\. Twitter may have changed its routing\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/notifications.js","lineNumber":30,"sourceCode":"        { name: 'limit', type: 'int', default: 20, help: 'Maximum number of notifications to return (default 20).' },\n    ],\n    columns: ['id', 'action', 'author', 'text', 'url'],\n    func: async (page, kwargs) => {\n        // 1. Navigate to home first (we need a loaded Twitter page for SPA navigation)\n        await page.goto('https://x.com/home');\n        await page.wait(3);\n        // 2. Install interceptor BEFORE SPA navigation\n        await page.installInterceptor('NotificationsTimeline');\n        // 3. SPA navigate to notifications via history API\n        await page.evaluate(`() => {\n        window.history.pushState({}, '', '/notifications');\n        window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));\n    }`);\n        await page.waitForCapture(5);\n        // Verify SPA navigation succeeded\n        const currentUrl = await page.evaluate('() => window.location.pathname');\n        if (currentUrl !== '/notifications') {\n            throw new CommandExecutionError('SPA navigation to notifications failed. Twitter may have changed its routing.');\n        }\n        // 4. Scroll to trigger pagination\n        await page.autoScroll({ times: 2, delayMs: 2000 });\n        // 5. Retrieve data\n        const requests = await page.getInterceptedRequests();\n        if (!requests || requests.length === 0)\n            return [];\n        let results = [];\n        const seen = new Set();\n        for (const req of requests) {\n            try {\n                // GraphQL response: { data: { viewer: ... } } (one level of .data)\n                let instructions = [];\n                if (req.data?.viewer?.timeline_response?.timeline?.instructions) {\n                    instructions = req.data.viewer.timeline_response.timeline.instructions;\n                }\n                else if (req.data?.viewer_v2?.user_results?.result?.notification_timeline?.timeline?.instructions) {\n                    instructions = req.data.viewer_v2.user_results.result.notification_timeline.timeline.instructions;","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/notifications.js#L12-L48","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the browser session is authenticated so /notifications does not redirect to /login.","Log the actual currentUrl to see where navigation landed and adapt the expected path.","Update the opencli twitter CLI if X renamed its notification route (check for updates/issues).","As a fallback, navigate directly with page.goto('https://x.com/notifications') and re-run, then report persistent routing failures."],"exampleFix":"// before\nif (currentUrl !== '/notifications') {\n    throw new CommandExecutionError('SPA navigation to notifications failed. Twitter may have changed its routing.');\n}\n// after\nif (currentUrl !== '/notifications') {\n    await page.goto('https://x.com/notifications');\n    const fallbackUrl = await page.evaluate('() => window.location.pathname');\n    if (!fallbackUrl.startsWith('/notifications')) {\n        throw new CommandExecutionError(`Notifications navigation failed, landed on: ${fallbackUrl}`);\n    }\n}","handlingStrategy":"try-catch","validationCode":"const path_ = await page.evaluate('() => window.location.pathname');\nif (!path_.startsWith('/notifications')) {\n  throw new Error(`Not on notifications (at ${path_}); check login state and route`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const notifs = await twitterNotifications();\n} catch (err) {\n  if (err.message.includes('SPA navigation to notifications failed')) {\n    console.error('Check that the session is logged in and X routing is unchanged; try direct page.goto fallback');\n  } else throw err;\n}","preventionTips":["Ensure an authenticated session so /notifications doesn't redirect to /login","Log the post-navigation pathname to detect X route changes early","Pin/track CLI updates when X changes its SPA routing"],"tags":["twitter","spa-navigation","routing","browser-automation"],"backgroundTag":"spa-route-navigation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}