Repository navigation
🔥 [🐛] Crashlytics iOS: JS crash lost on device when chaining is disabled (crashWithStackPromise exits before the report is saved) #9360
Description
Issue
With crashlytics_javascript_exception_handler_chaining_enabled: false, an unhandled JS error on a real iOS device never reaches the Crashlytics console. The same build on the iOS simulator reports it fine, and native crashes (crash()) from the same device arrive within a minute.
Steps
firebase.json:{ "react-native": { "crashlytics_javascript_exception_handler_chaining_enabled": false } }- In a Release build, throw from a press handler:
import { getCrashlytics, log } from '@react-native-firebase/crashlytics'; <Pressable onPress={() => { log(getCrashlytics(), 'about to throw'); throw new Error('Test JS crash'); }} />
- Run it on an iPhone, without a debugger attached, and tap.
- Relaunch the app and wait.
Expected: a fatal JavaScriptError: Test JS crash with the JS stack, as on the simulator.
Actual: nothing arrives: no fatal, no non-fatal, after several relaunches and more than 10 minutes. The device console shows the library recording and exiting:
-[RNFBCrashlyticsModule crashWithStackPromise:resolve:reject:] [Line 106] Crashlytics - Crash logged. Terminating app.
The app terminated with the exit code 0.
Tried twice on the device, with the same result both times. In between, a native crash() from the same install arrived, so collection, upload, and the app's Crashlytics setup all work.
Likely cause: crashWithStackPromise calls recordJavaScriptError: and then exit(0) straight away (RNFBCrashlyticsModule.mm, 26.4.0):
[self recordJavaScriptError:jsErrorDict];
ELog(@"Crashlytics - Crash logged. Terminating app.");
exit(0);recordJavaScriptError: goes through Crashlytics' recordExceptionModel:, which saves the report asynchronously. On a device, exit(0) seems to end the process before that save completes. The faster simulator wins the race, which would explain why it works there.
Workaround: crashlytics_javascript_exception_handler_chaining_enabled: true. The same tap on the same iPhone then delivers two reports: a fatal RCTFatalException: Unhandled JS Exception: Error: … (React Native's abort), and a non-fatal JavaScriptError with the JS stack. Presumably the slower abort path gives the save time to finish. The cost is two reports per JS error, with the JS stack only in the non-fatal's frames.
Possibly related: #8898 (iOS crashes not shown in the console; closed as stale without a diagnosis).
Project Files
Javascript
Click To Expand
package.json:
{
"dependencies": {
"@react-native-firebase/analytics": "26.4.0",
"@react-native-firebase/app": "26.4.0",
"@react-native-firebase/crashlytics": "26.4.0",
"@react-native-firebase/remote-config": "26.4.0",
"expo": "~57.0.25",
"react": "19.2.3",
"react-native": "0.86.3"
}
}firebase.json for react-native-firebase v6:
{
"react-native": {
"crashlytics_javascript_exception_handler_chaining_enabled": false,
"crashlytics_debug_enabled": false
}
}iOS
Click To Expand
ios/Podfile:
- I'm not using Pods
- I'm using Pods and my Podfile looks like:
# Expo prebuild (CNG). Relevant config:
# ["@react-native-firebase/app", { "ios": { "disableSPM": true } }]
# "@react-native-firebase/crashlytics"
# ["@react-native-firebase/analytics", { "ios": { "withoutAdIdSupport": true } }]
# ["expo-build-properties", { "ios": { "useFrameworks": "static",
# "forceStaticLinking": ["RNFBApp","RNFBAnalytics","RNFBCrashlytics","RNFBRemoteConfig"] } }]
# Firebase iOS SDK resolved by CocoaPods: 12.18.0. New Architecture, Hermes.AppDelegate.m:
// Swift AppDelegate from Expo SDK 57; the RNFB config plugin adds FirebaseApp.configure()
// in didFinishLaunchingWithOptions.Android
Click To Expand
Have you converted to AndroidX?
- my application is an AndroidX application?
- I am using
android/gradle.settingsjetifier=truefor Android compatibility? - I am using the NPM package
jetifierfor react-native compatibility?
android/build.gradle:
// N/A (iOS only)android/app/build.gradle:
// N/Aandroid/settings.gradle:
// N/AMainApplication.java:
// N/AAndroidManifest.xml:
<!-- N/A -->Environment
Click To Expand
react-native info output:
React Native 0.86.3 (New Architecture, Hermes), Expo SDK 57.0.25
Xcode 27.0 (27A266a), macOS 26.6.1
Device: iPhone 16e, iOS 26.7 (fails). Simulator: iPhone 17, iOS 27.0 (works)
- Platform that you're experiencing the issue on:
- iOS
- Android
- iOS but have not tested behavior on Android
- Android but have not tested behavior on iOS
- Both
react-native-firebaseversion you're using that has this issue:26.4.0
Firebasemodule(s) you're using that has the issue:Crashlytics
- Are you using
TypeScript?Y&6.0.3
Activity
Mastersam07 commentedon Oct 19, 2026
Hi @innolight, thanks for the detailed report.
I traced it, and I think you're right that it's a race. The detail is slightly different from what you described:
crashWithStackPromisecallsrecordJavaScriptError:, which calls[[FIRCrashlytics crashlytics] recordExceptionModel:], and then callsexit(0)straight away.recordExceptionModel:doesn't write the report straight away. It goes throughwaitForContextInit:, which runs the write inside a promise'sthen:callback, and those callbacks are dispatched onto the main queue.methodQueue, socrashWithStackPromiseruns on its own background queue, andexit(0)fires there while the write is still waiting on the main thread.On a real device the main thread is likely still busy, so the process exits before the report is saved. On a fast simulator the main thread usually gets to it first. This also explains why your workaround works: with chaining enabled, React Native's abort path is slow enough for the write to finish.
Workaround for now: keep
crashlytics_javascript_exception_handler_chaining_enabled: true, as you're doing.Next steps on our side: we're looking at making sure the report is saved before the process exits. I'll post here when there's an update.