Description
When the app is cold-started by tapping a push notification (i.e. Android had killed the process while idle), the app navigates to the last active chat rather than the chat referenced in the notification. The correct chat is visible if you press the back button from that screen, meaning the navigation did eventually happen — but underneath the wrong screen.
Reported by dbarrett@expensify.com on Android staging, 2026-05-29 ~13:59 UTC. Tapped a notification from Florent, but the app opened to Chronos (last active chat). Pressing back from Chronos revealed the Florent chat.
Root Cause (confirmed via VictoriaLogs)
This is a cold-start race condition introduced by the dynamic import added in #84624.
subscribeToPushNotifications is now lazily imported inside a useEffect gated on splashScreenState === HIDDEN:
useEffect(() => {
if (splashScreenState !== HIDDEN) return;
import('.../subscribeToPushNotifications');
}, [splashScreenState]);
When the user taps a notification to cold-launch the app, the Airship native bridge fires notification_response before the splash screen hides and before PushNotification.onSelected(REPORT_COMMENT, navigateToReport) has been registered. The tap event is dispatched into the void — navigateToReport is never called.
Log Evidence
At 13:59:38.491Z the device correctly emitted:
[PushNotification] Callback triggered for com.airship.notification_response
But none of the subsequent JS-side logs appeared:
- ❌
[PushNotification] Navigating to report
- ❌
[PushNotification] onSelected() - Navigation is ready. Navigating...
- ❌
[PushNotification] Not navigating because this is a singleNewDotEntry flow
- ❌
[PushNotification] onSelected() - failed
Since [PushNotification] Navigating to report is the first line of navigateToReport (before any await or guard), its absence proves the JS handler was never invoked — not just delayed.
Two earlier taps the same day (00:48 UTC and 03:29 UTC, when the app was warm) worked correctly, confirming the regression is specific to cold starts.
Two corroborating signals for cold start:
- Previous push activity was at 12:44 UTC — 75 minutes of silence before the broken tap, enough for Android to kill the process.
- A new staging build (
ecash9.3.89-2) appeared between the broken tap and the next session, suggesting the 13:59 launch may also have been triggered by an app update.
Steps to Reproduce
- Open the app on Android, navigate to any chat, then background the app and leave it idle for ~1 hour (or force-kill it).
- Receive a push notification for a different chat.
- Tap the notification.
Expected: App opens directly to the notification's chat.
Actual: App opens to the last active chat. The notification's chat is reachable via the back button.
Relevant Code
Platform
- Platform: Android
- Environment: Staging (likely also affects production)
- App version at time of report:
ecash9.3.83-1
Upwork Automation - Do Not Edit
Description
When the app is cold-started by tapping a push notification (i.e. Android had killed the process while idle), the app navigates to the last active chat rather than the chat referenced in the notification. The correct chat is visible if you press the back button from that screen, meaning the navigation did eventually happen — but underneath the wrong screen.
Reported by
dbarrett@expensify.comon Android staging, 2026-05-29 ~13:59 UTC. Tapped a notification from Florent, but the app opened to Chronos (last active chat). Pressing back from Chronos revealed the Florent chat.Root Cause (confirmed via VictoriaLogs)
This is a cold-start race condition introduced by the dynamic import added in #84624.
subscribeToPushNotificationsis now lazily imported inside auseEffectgated onsplashScreenState === HIDDEN:When the user taps a notification to cold-launch the app, the Airship native bridge fires
notification_responsebefore the splash screen hides and beforePushNotification.onSelected(REPORT_COMMENT, navigateToReport)has been registered. The tap event is dispatched into the void —navigateToReportis never called.Log Evidence
At 13:59:38.491Z the device correctly emitted:
But none of the subsequent JS-side logs appeared:
[PushNotification] Navigating to report[PushNotification] onSelected() - Navigation is ready. Navigating...[PushNotification] Not navigating because this is a singleNewDotEntry flow[PushNotification] onSelected() - failedSince
[PushNotification] Navigating to reportis the first line ofnavigateToReport(before anyawaitor guard), its absence proves the JS handler was never invoked — not just delayed.Two earlier taps the same day (00:48 UTC and 03:29 UTC, when the app was warm) worked correctly, confirming the regression is specific to cold starts.
Two corroborating signals for cold start:
ecash9.3.89-2) appeared between the broken tap and the next session, suggesting the 13:59 launch may also have been triggered by an app update.Steps to Reproduce
Expected: App opens directly to the notification's chat.
Actual: App opens to the last active chat. The notification's chat is reachable via the back button.
Relevant Code
subscribeToPushNotifications.ts#L113—navigateToReporthandlerPlatform
ecash9.3.83-1Upwork Automation - Do Not Edit