fix(android): Capture native exceptions consumed by Expo's bridgeless error handling#5871
fix(android): Capture native exceptions consumed by Expo's bridgeless error handling#5871
Conversation
…s error handling On Expo SDK 53+ Android, ExpoReactHostDelegate.handleInstanceException iterates registered ReactNativeHostHandlers but does not rethrow the exception. This means native crashes (e.g. IllegalStateException from Fabric's SurfaceMountingManager) caught by React Native's GuardedFrameCallback never reach Java's UncaughtExceptionHandler, which sentry-java relies on for crash capture. Register a ReactNativeHostHandler via Expo's Package system that intercepts these exceptions and captures them directly through Sentry.captureException with an unhandled mechanism (type="expoReactHost", handled=false) using ExceptionMechanismException. The Expo-specific code lives in a conditional src/expo source set that is only compiled when expo-modules-core is present as a Gradle project. Non-Expo React Native builds are completely unaffected. Expo's autolinking discovers SentryExpoPackage automatically via the new expo-module.config.json.
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
Plus 3 more 🤖 This preview updates automatically when you update the PR. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing try/catch around Sentry calls in error handler
- Wrapped Expo host exception capture logic in a Throwable try/catch to prevent Sentry instrumentation failures from propagating and crashing the app.
Or push these changes by commenting:
@cursor push 70a42a9417
Preview (70a42a9417)
diff --git a/packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java b/packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java
--- a/packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java
+++ b/packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java
@@ -32,13 +32,17 @@
return;
}
- final Mechanism mechanism = new Mechanism();
- mechanism.setType(MECHANISM_TYPE);
- mechanism.setHandled(false);
+ try { // NOPMD - We don't want to crash in any case
+ final Mechanism mechanism = new Mechanism();
+ mechanism.setType(MECHANISM_TYPE);
+ mechanism.setHandled(false);
- final ExceptionMechanismException mechanismException =
- new ExceptionMechanismException(mechanism, exception, Thread.currentThread());
+ final ExceptionMechanismException mechanismException =
+ new ExceptionMechanismException(mechanism, exception, Thread.currentThread());
- Sentry.captureException(mechanismException);
+ Sentry.captureException(mechanismException);
+ } catch (Throwable ignored) { // NOPMD - We don't want to crash in any case
+ // ignore
+ }
}
}This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java
Show resolved
Hide resolved
antonis
left a comment
There was a problem hiding this comment.
The fix LGTM. I think with the cursor feedback addressed and scoping the changelog would be ready to 🚢
packages/core/android/src/expo/java/io/sentry/react/expo/SentryReactNativeHostHandler.java
Outdated
Show resolved
Hide resolved
| @@ -0,0 +1,44 @@ | |||
| package io.sentry.react.expo; | |||
There was a problem hiding this comment.
Would you be able to add some tests for this file?
There was a problem hiding this comment.
Added unit tests in the latest commit (9567e56). Tests cover:
- Skip capture in dev support mode
- Skip capture when Sentry is not enabled
- Capture with correct unhandled mechanism (
type="expoReactHost",handled=false) - Graceful handling when
Sentry.captureExceptionthrows (the new try/catch)
Created an expo-stubs.jar with minimal Package and ReactNativeHostHandler interfaces for the test project (following the existing replay-stubs.jar pattern).
There was a problem hiding this comment.
nit: Wdyt of adding a short readme with the context on what is the jar file and how to regenerate it if needed (similar to replay-stubs)?
There was a problem hiding this comment.
I didn't even know there is a document descrbing replay-stubs! :) Will check it out.
- Wrap Sentry calls in try/catch to follow the project's defensive
coding pattern ("never let Sentry crash the host app")
- Scope changelog entry to Android
- Add unit tests for SentryReactNativeHostHandler covering:
- Skip capture in dev support mode
- Skip capture when Sentry is not enabled
- Capture with unhandled mechanism when enabled
- Graceful handling when Sentry.captureException throws
- Add expo-stubs.jar to test project for compile-time interfaces
...ntryAndroidTester/app/src/test/java/io/sentry/react/expo/SentryReactNativeHostHandlerTest.kt
Show resolved
Hide resolved
|
Ok, I've replaced the conditional @antonis @lucas-zimerman please check :) |
...re/android/expo-stubs/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.java
Outdated
Show resolved
Hide resolved
antonis
left a comment
There was a problem hiding this comment.
Let's fix the lint issue and run he full test suite.
Other than that LGTM 🚀


📢 Type of change
📜 Description
Fixes #5868
On Expo SDK 53+ Android apps,
ExpoReactHostDelegate.handleInstanceExceptioniterates registeredReactNativeHostHandlers but does not rethrow the exception. This causes native crashes (e.g.,IllegalStateExceptionfrom Fabric'sSurfaceMountingManager) caught by React Native'sGuardedFrameCallbackto be silently swallowed — they never reach Java'sUncaughtExceptionHandler, whichsentry-javarelies on for crash capture.This PR registers a
ReactNativeHostHandlervia Expo's Package system that intercepts these exceptions and captures them directly throughSentry.captureException().📝 Checklist
sendDefaultPIIis enabled