From aa2a6260caf622893fbb5d312a119ded90d467fc Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Wed, 13 May 2026 09:52:33 +0200 Subject: [PATCH] fix(ios): Filter ExceptionsManager.reportException duplicates in app-start init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the native SDK is initialized early via sentry.options.json (the v8 "Capture App Start Errors" feature, #5582), unhandled JS errors on React Native's New Architecture are wrapped in a C++ exception and captured by the native crash handler. The deduplication added in #5532 / 7.9.0 lived only in RNSentry.mm's prepareOptions (the JS-initiated init path), so the RNSentryStart.updateWithReactFinals path used by the file-based and `autoInitializeNativeSdk: false` flows let the C++ wrapper through and users saw two events per JS error — one JS and one C++. Port the `ExceptionsManager.reportException` value-based filter into `updateWithReactFinals` so both init paths apply the same dedup. Mirror the test coverage in RNSentryStartTests.swift, exercising both `RNSentrySDK.start(configureOptions:)` and the dictionary-based `RNSentryStart.start(options:)` (file-based) paths. Android is unaffected: `updateWithReactDefaults` registers `addIgnoredExceptionForType(JavascriptException.class)` which is shared by both Android init paths. Fixes #6116. --- CHANGELOG.md | 1 + .../RNSentryStartTests.swift | 46 +++++++++++++++++++ packages/core/ios/RNSentryStart.m | 13 ++++++ 3 files changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd616a599..bf9c39a749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes +- Fix duplicate JS error reporting on iOS New Architecture when the native SDK is initialized early via `sentry.options.json` ("Capture App Start Errors"). The `ExceptionsManager.reportException` C++ wrapper filter is now applied in both init paths, matching the JS-init dedup added in `7.9.0` ([#6116](https://github.com/getsentry/sentry-react-native/issues/6116)) - Fix the issue with uploading iOS Debug Symbols in EAS Build when using pnpm ([#6076](https://github.com/getsentry/sentry-react-native/issues/6076)) - Improve frame delay collection performance by using sentry-java `getFramesDelay` API ([#6074](https://github.com/getsentry/sentry-react-native/pull/6074)) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index c437b83049..86673ef206 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -121,6 +121,52 @@ final class RNSentryStartTests: XCTestCase { } } + func testStartIgnoresJsErrorCppExceptionWrapper() throws { + // Reproduces getsentry/sentry-react-native#6116: + // When the native SDK is started early via sentry.options.json ("Capture App Start + // Errors"), New Architecture wraps unhandled JS errors in a C++ exception that the + // native crash handler captures. Without dedup in updateWithReactFinals, both the JS + // event and the C++ wrapper are sent. Cover both init paths to keep them aligned. + let testCases: [() throws -> Void] = [ + { + RNSentrySDK.start { options in + options.dsn = "https://abcd@efgh.ingest.sentry.io/123456" + } + }, + { + try self.startFromRN(options: [ + "dsn": "https://abcd@efgh.ingest.sentry.io/123456" + ]) + } + ] + + for startMethod in testCases { + try startMethod() + + let actualOptions = PrivateSentrySDKOnly.options + + let cppWrapperEvent = Event() + cppWrapperEvent.exceptions = [ + Exception( + value: "N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an exception: Unhandled JS Exception: Error: Test error", + type: "C++ Exception" + ) + ] + XCTAssertNil(actualOptions.beforeSend!(cppWrapperEvent), + "Event with ExceptionsManager.reportException in value should be dropped") + + let legitimateCppEvent = Event() + legitimateCppEvent.exceptions = [ + Exception( + value: "std::runtime_error: Some other C++ error occurred", + type: "C++ Exception" + ) + ] + XCTAssertNotNil(actualOptions.beforeSend!(legitimateCppEvent), + "Legitimate C++ exception without ExceptionsManager.reportException should not be dropped") + } + } + func testStartSetsNativeEventOrigin() throws { let testCases: [() throws -> Void] = [ { diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 6763d2e072..c2485cf990 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -203,6 +203,19 @@ + (void)updateWithReactFinals:(SentryOptions *)options return nil; } + // With New Architecture, React Native wraps JS errors in C++ exceptions. + // These exceptions are caught by the native crash handler and should be filtered out + // since the JS error is already reported by the JS error handler. + // The key indicator is "ExceptionsManager.reportException" in the exception value, + // which is React Native's mechanism for reporting JS errors to the native layer. + for (SentryException *exception in event.exceptions) { + if (nil != exception.value && + [exception.value rangeOfString:@"ExceptionsManager.reportException"].location + != NSNotFound) { + return nil; + } + } + [self setEventOriginTag:event]; if (userBeforeSend == nil) { return event;