From f24926f1d17cdc3511651cd90a0899a0a57c7f1e Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 26 Mar 2026 12:48:13 +0100 Subject: [PATCH 1/2] fix(android): Capture native exceptions swallowed by Expo's bridgeless error handling On Expo SDK 53+ Android, ExpoReactHostDelegate.handleInstanceException iterates registered ReactNativeHostHandlers but does not rethrow. This means native crashes caught by React Native's GuardedFrameCallback never reach Java's UncaughtExceptionHandler, which sentry-java relies on. Register a ReactNativeHostHandler via Expo's Package system that captures these exceptions through Sentry.captureException with an unhandled mechanism (type=expoReactHost, handled=false). The handler lives in a separate Android library project (android/expo-handler/) to avoid Gradle codegen conflicts between Expo and RN autolinking. The expo-module.config.json registers it as :sentry-react-native-expo. Non-Expo builds are unaffected. --- CHANGELOG.md | 2 +- .../RNSentryAndroidTester/app/build.gradle | 2 +- packages/core/android/build.gradle | 2 -- .../core/android/expo-handler/build.gradle | 20 ++++++++++++++++++ .../sentry/react/expo/SentryExpoPackage.java | 0 .../expo/SentryReactNativeHostHandler.java | 0 packages/core/android/expo-stubs/README.md | 4 ++-- packages/core/android/libs/expo-stubs.jar | Bin 1667 -> 1667 bytes packages/core/expo-module.config.json | 6 +++++- packages/core/package.json | 3 +-- 10 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 packages/core/android/expo-handler/build.gradle rename packages/core/android/{src/expo => expo-handler/src/main}/java/io/sentry/react/expo/SentryExpoPackage.java (100%) rename packages/core/android/{src/expo => expo-handler/src/main}/java/io/sentry/react/expo/SentryReactNativeHostHandler.java (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c61eea66..ecb2a5a68c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ### Fixes - Session replay will no longer start recording when an event was ignored or dropped. ([#5885](https://github.com/getsentry/sentry-react-native/pull/5885)) -- Capture native exceptions consumed by Expo's bridgeless error handling on Android ([#5871](https://github.com/getsentry/sentry-react-native/pull/5871)) +- Capture native exceptions consumed by Expo's bridgeless error handling on Android ([#5898](https://github.com/getsentry/sentry-react-native/pull/5898)) - Fix SIGABRT crash on launch when `mobileReplayIntegration` is not configured and iOS deployment target >= 16.0 ([#5858](https://github.com/getsentry/sentry-react-native/pull/5858)) - Reduce `reactNavigationIntegration` performance overhead ([#5840](https://github.com/getsentry/sentry-react-native/pull/5840), [#5842](https://github.com/getsentry/sentry-react-native/pull/5842), [#5849](https://github.com/getsentry/sentry-react-native/pull/5849)) - Fix duplicated breadcrumbs on Android ([#5841](https://github.com/getsentry/sentry-react-native/pull/5841)) diff --git a/packages/core/RNSentryAndroidTester/app/build.gradle b/packages/core/RNSentryAndroidTester/app/build.gradle index 53173e7034..9a1ba87087 100644 --- a/packages/core/RNSentryAndroidTester/app/build.gradle +++ b/packages/core/RNSentryAndroidTester/app/build.gradle @@ -37,7 +37,7 @@ android { } } -android.sourceSets.main.java.srcDirs += ['../../android/src/expo/java'] +android.sourceSets.main.java.srcDirs += ['../../android/expo-handler/src/main/java'] dependencies { implementation project(':RNSentry') diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 27e18d53ec..8f080dd7b7 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -48,14 +48,12 @@ android { } else { java.srcDirs += ['src/oldarch'] } - java.srcDirs += ['src/expo'] } } } dependencies { compileOnly files('libs/replay-stubs.jar') - compileOnly files('libs/expo-stubs.jar') implementation 'com.facebook.react:react-native:+' api 'io.sentry:sentry-android:8.36.0' debugImplementation 'io.sentry:sentry-spotlight:8.36.0' diff --git a/packages/core/android/expo-handler/build.gradle b/packages/core/android/expo-handler/build.gradle new file mode 100644 index 0000000000..30d7c24688 --- /dev/null +++ b/packages/core/android/expo-handler/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 31 + namespace = "io.sentry.react.expo" + + defaultConfig { + minSdkVersion 21 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + compileOnly project(':expo-modules-core') + compileOnly 'io.sentry:sentry-android:8.36.0' +} diff --git a/packages/core/android/src/expo/java/io/sentry/react/expo/SentryExpoPackage.java b/packages/core/android/expo-handler/src/main/java/io/sentry/react/expo/SentryExpoPackage.java similarity index 100% rename from packages/core/android/src/expo/java/io/sentry/react/expo/SentryExpoPackage.java rename to packages/core/android/expo-handler/src/main/java/io/sentry/react/expo/SentryExpoPackage.java diff --git a/packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java b/packages/core/android/expo-handler/src/main/java/io/sentry/react/expo/SentryReactNativeHostHandler.java similarity index 100% rename from packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java rename to packages/core/android/expo-handler/src/main/java/io/sentry/react/expo/SentryReactNativeHostHandler.java diff --git a/packages/core/android/expo-stubs/README.md b/packages/core/android/expo-stubs/README.md index 501a77caab..26dd27c3cf 100644 --- a/packages/core/android/expo-stubs/README.md +++ b/packages/core/android/expo-stubs/README.md @@ -1,6 +1,6 @@ -This module provides stubs for `expo-modules-core` interfaces (`Package` and `ReactNativeHostHandler`) needed to compile the Expo-specific source set (`android/src/expo/`). +This module provides stubs for `expo-modules-core` interfaces (`Package` and `ReactNativeHostHandler`) needed to compile the Expo handler tests in `RNSentryAndroidTester`. -The Expo source set registers a `ReactNativeHostHandler` that captures native exceptions swallowed by Expo's bridgeless error handling (`ExpoReactHostDelegate.handleInstanceException`). These stubs are added as a `compileOnly` dependency to `android/build.gradle` (meaning, they are not present at runtime). In Expo projects, the real `expo-modules-core` classes are available at runtime via Expo's autolinking. +The Expo handler (`android/expo-handler/`) registers a `ReactNativeHostHandler` that captures native exceptions swallowed by Expo's bridgeless error handling (`ExpoReactHostDelegate.handleInstanceException`). In Expo projects, the handler is compiled against the real `expo-modules-core` project. For unit testing in `RNSentryAndroidTester` (which doesn't have Expo), these stubs provide the interfaces at compile time. ## Updating the stubs diff --git a/packages/core/android/libs/expo-stubs.jar b/packages/core/android/libs/expo-stubs.jar index 522855527eb2d092bcfa947198639976a9bf5ce5..37bb1d4cc63aa443747e6efd21842e20c085ecdd 100644 GIT binary patch delta 29 kcmZqXZRX_(@MdNaVPN3kVCYWT$YaC8)Sa~1i^Y-&098r`2mk;8 delta 20 bcmZqXZRVZ$OKPK(9?N7FMuE){EZR%}MaTuG diff --git a/packages/core/expo-module.config.json b/packages/core/expo-module.config.json index 339d64e117..c3de4e2b5b 100644 --- a/packages/core/expo-module.config.json +++ b/packages/core/expo-module.config.json @@ -1,3 +1,7 @@ { - "platforms": ["android"] + "platforms": ["android"], + "android": { + "name": "sentry-react-native-expo", + "path": "android/expo-handler" + } } diff --git a/packages/core/package.json b/packages/core/package.json index ad38ba26ac..754a6fe2b1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,14 +18,13 @@ }, "main": "dist/js/index.js", "scripts": { - "build": "npx run-s build:sdk downlevel build:tools build:plugin build:replay-stubs build:expo-stubs", + "build": "npx run-s build:sdk downlevel build:tools build:plugin build:replay-stubs", "build:sdk": "tsc -p tsconfig.build.json", "build:sdk:watch": "tsc -p tsconfig.build.json -w --preserveWatchOutput", "build:tools": "tsc -p tsconfig.build.tools.json", "build:tools:watch": "tsc -p tsconfig.build.tools.json -w --preserveWatchOutput", "build:plugin": "EXPO_NONINTERACTIVE=true expo-module build plugin", "build:replay-stubs": "cd android/replay-stubs && ./gradlew jar", - "build:expo-stubs": "cd android/expo-stubs && ./gradlew jar", "build:tarball": "bash scripts/build-tarball.sh", "downlevel": "downlevel-dts dist ts3.8/dist --to=3.8", "clean": "rimraf dist coverage && yarn clean:plugin", From 4b01cd5642cd93eb11fcebc4796630bd05c991a8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 26 Mar 2026 12:59:27 +0100 Subject: [PATCH 2/2] Fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb2a5a68c..03c61eea66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ### Fixes - Session replay will no longer start recording when an event was ignored or dropped. ([#5885](https://github.com/getsentry/sentry-react-native/pull/5885)) -- Capture native exceptions consumed by Expo's bridgeless error handling on Android ([#5898](https://github.com/getsentry/sentry-react-native/pull/5898)) +- Capture native exceptions consumed by Expo's bridgeless error handling on Android ([#5871](https://github.com/getsentry/sentry-react-native/pull/5871)) - Fix SIGABRT crash on launch when `mobileReplayIntegration` is not configured and iOS deployment target >= 16.0 ([#5858](https://github.com/getsentry/sentry-react-native/pull/5858)) - Reduce `reactNavigationIntegration` performance overhead ([#5840](https://github.com/getsentry/sentry-react-native/pull/5840), [#5842](https://github.com/getsentry/sentry-react-native/pull/5842), [#5849](https://github.com/getsentry/sentry-react-native/pull/5849)) - Fix duplicated breadcrumbs on Android ([#5841](https://github.com/getsentry/sentry-react-native/pull/5841))