Skip to content

🔥 [🐛] Crashlytics iOS: JS crash lost on device when chaining is disabled (crashWithStackPromise exits before the report is saved) #9360

Open

Description

@innolight

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

  1. firebase.json:
    { "react-native": { "crashlytics_javascript_exception_handler_chaining_enabled": false } }
  2. 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');
    }} />
  3. Run it on an iPhone, without a debugger attached, and tap.
  4. 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.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A (iOS only)

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.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-firebase version you're using that has this issue:
    • 26.4.0
  • Firebase module(s) you're using that has the issue:
    • Crashlytics
  • Are you using TypeScript?
    • Y & 6.0.3

Activity

  1. self-assigned this
    on Oct 19, 2026
  2. Mastersam07 commented on Oct 19, 2026

    @Mastersam07
    Contributor

    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:

    1. crashWithStackPromise calls recordJavaScriptError:, which calls [[FIRCrashlytics crashlytics] recordExceptionModel:], and then calls exit(0) straight away.
    2. In firebase-ios-sdk 12.x (checked in 12.19.0), recordExceptionModel: doesn't write the report straight away. It goes through waitForContextInit:, which runs the write inside a promise's then: callback, and those callbacks are dispatched onto the main queue.
    3. Our module doesn't set a methodQueue, so crashWithStackPromise runs on its own background queue, and exit(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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions