Skip to content

fix(android): Capture native exceptions consumed by Expo's bridgeless error handling#5871

Merged
alwx merged 9 commits intomainfrom
alwx/improvement/5868
Mar 26, 2026
Merged

fix(android): Capture native exceptions consumed by Expo's bridgeless error handling#5871
alwx merged 9 commits intomainfrom
alwx/improvement/5868

Conversation

@alwx
Copy link
Contributor

@alwx alwx commented Mar 24, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Fixes #5868

On Expo SDK 53+ Android apps, ExpoReactHostDelegate.handleInstanceException iterates registered ReactNativeHostHandlers but does not rethrow the exception. This causes native crashes (e.g., IllegalStateException from Fabric's SurfaceMountingManager) caught by React Native's GuardedFrameCallback to be silently swallowed — they never reach Java's UncaughtExceptionHandler, which sentry-java relies on for crash capture.

This PR registers a ReactNativeHostHandler via Expo's Package system that intercepts these exceptions and captures them directly through Sentry.captureException().

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

…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.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • fix(android): Capture native exceptions consumed by Expo's bridgeless error handling by alwx in #5871
  • chore(core): Upgrade TypeScript from 4.9.5 to 5.9.3 by antonis in #5886
  • chore(deps): update JavaScript SDK to v10.46.0 by github-actions in #5890
  • chore(deps): update CLI to v3.3.4 by github-actions in #5891
  • chore(deps): bump picomatch from 2.3.1 to 2.3.2 by dependabot in #5889
  • fix(core): Fix stale deprecation references by antonis in #5881
  • chore(build): Rename version-bump.js to bump-version.js by antonis in #5883
  • chore(deps): bump activesupport from 7.0.8.6 to 7.2.3.1 in /samples/react-native-macos by dependabot in #5870
  • chore(deps): bump activesupport from 6.1.7.10 to 7.2.3.1 in /samples/react-native by dependabot in #5869
  • chore(deps): bump yauzl to ^3.2.1 by antonis in #5855
  • chore(deps): bump appium from 2.4.1 to 3.2.2 by antonis in #5856
  • fix(ios): Guard replay postInit behind runtime session replay check by antonis in #5858
  • Add better needs_web check to CI by alwx in #5863
  • chore(deps): bump fast-xml-parser to ^5.5.7 by antonis in #5854
  • CI: detect-changes workflow to only check the affected components on the CI side by alwx in #5843
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.0 by dependabot in #5861
  • chore(deps): bump getsentry/craft from 2.24.1 to 2.25.0 by dependabot in #5862
  • chore(deps): bump github/codeql-action from 4.32.6 to 4.34.1 by dependabot in #5860
  • chore(deps): update JavaScript SDK to v10.45.0 by github-actions in #5848
  • chore(deps): bump flatted from 3.4.1 to 3.4.2 by dependabot in #5853
  • chore(deps): update Cocoa SDK to v9.8.0 by github-actions in #5847
  • fix(tracing): Guard getNewScreenTimeToDisplay behind enableTimeToInitialDisplay by antonis in #5849
  • chore(deps): bump json from 2.16.0 to 2.17.1.2 in /performance-tests by dependabot in #5844
  • chore(docs): Add changelog entry for duplicated breadcrumbs fix by antonis in #5851

Plus 3 more


🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request

Generated by 🚫 dangerJS against 5811daf

@alwx alwx changed the title fix(android): Capture native exceptions swallowed by Expo's bridgeless error handling fix(android): Capture native exceptions consumed by Expo's bridgeless error handling Mar 24, 2026
@alwx alwx marked this pull request as ready for review March 24, 2026 10:24
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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.

Create PR

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.

@alwx alwx self-assigned this Mar 24, 2026
Copy link
Contributor

@antonis antonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix LGTM. I think with the cursor feedback addressed and scoping the changelog would be ready to 🚢

@@ -0,0 +1,44 @@
package io.sentry.react.expo;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to add some tests for this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.captureException throws (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).

Copy link
Contributor

@antonis antonis Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)?

Copy link
Contributor Author

@alwx alwx Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@alwx
Copy link
Contributor Author

alwx commented Mar 26, 2026

Ok, I've replaced the conditional findProject(':expo-modules-core') approach with a compileOnly stubs jar (matching the existing replay-stubs pattern). Now it's more or less the same + there is also a README like it's done for replay-stubs

@antonis @lucas-zimerman please check :)

@alwx alwx requested review from antonis and lucas-zimerman March 26, 2026 08:18
Copy link
Contributor

@antonis antonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fix the lint issue and run he full test suite.

Other than that LGTM 🚀

@alwx alwx merged commit a688ab9 into main Mar 26, 2026
36 of 49 checks passed
@alwx alwx deleted the alwx/improvement/5868 branch March 26, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support capturing native Android exceptions intercepted by Expo's error handler

3 participants