Repository navigation
fix(mobile): apply RN ExceptionsManager Hermes fix via patch-package - #14643
Merged
Merged
Conversation
patch-react-native-exceptions.js targeted packages/mobile/node_modules/react-native, but react-native is hoisted to the root node_modules, so the script exited 0 without patching anything. Replace it with patches/react-native+0.79.5.patch, which patch-package --error-on-fail applies in postinstall and fails on if it stops applying. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
dylanjeffers
added a commit
that referenced
this pull request
Oct 9, 2026
Phase 1 of the React Native upgrade: 0.79.5 → 0.81.6 with the old architecture still on. **Base: `chore/mobile-native-release-1.5.188` (#14654).** Rebased onto it after 1.5.187 (#14645, which squashed #14627, #14640 and #14650) merged to main; the two lib-swap commits are gone from this branch. Merge #14647 and #14651 into this branch first, then this into #14654, then #14654 into main as one push (see #14654 for the full order). ## Rebase onto 1.5.187 (2026-10-08) - `patches/react-native+0.79.5.patch` (the ExceptionsManager Hermes fix from #14643) is renamed to `react-native+0.81.6.patch`. The anchors are unchanged in 0.81.6, and `scripts/patch-package.sh` applies it with `--error-on-fail` during `npm install` (checked: `getErrorStackSafe` is in the installed `ExceptionsManager.js`). - notifee and Firebase from #14640 are kept as they are: `@notifee/react-native` 9.1.8 is the latest release and `@react-native-firebase/app`/`messaging` 24.1.1 build on 0.81.6 on the old arch (both declare `react-native: *`). No bump needed. - The Firebase Android BoM stays pinned to 33.16.0. This branch moves Kotlin to 2.1.20, and BoM 34 needs 2.2, so the pin still applies; the comment now says so. - The UIScene and notifee lines in `AppDelegate.mm` (#14626, #14640) are untouched by this branch. - `package-lock.json` and `Podfile.lock` were regenerated (`npm install`, then `RCT_NEW_ARCH_ENABLED=0 bundle exec pod install`); the release xcconfig has no `RCT_NEW_ARCH_ENABLED=1`. The SwiftAudioEx post_install patch (#14650) still applies. **Needs a native release (1.5.188).** Don't OTA this JS onto older binaries. Old architecture stays on: Android `newArchEnabled=false`, iOS `RCTNewArchEnabled=false`, pods installed with `RCT_NEW_ARCH_ENABLED=0`. Two commits, one per step. Each step builds on iOS and Android: 1. 0.79.5 → 0.80.3 2. 0.80.3 → 0.81.6 (0.81.6 is the latest 0.81 patch on npm) ## Version bumps | Package | From | To | Why | |---|---|---|---| | react-native | 0.79.5 | 0.81.6 | the upgrade | | `@react-native/*` (babel-preset, eslint-config, metro-config, metro-babel-transformer, typescript-config) | 0.79.5 | 0.81.6 | must match RN | | `@react-native-community/cli`, `cli-platform-android`, `cli-platform-ios` | 18.0.0 | 20.1.3 | CLI line for 0.81; the platform packages were left at 18 on the old ladder branch | | react, react-dom, react-test-renderer (whole monorepo, root `overrides` too) | 19.0.0 | **19.1.4** | RN's bundled renderer throws at startup unless `react` is **exactly** its version: 19.1.0 for 0.80.3, 19.1.4 for 0.81.6. The old ladder branch used 19.1.8, which would throw "Incompatible React versions" on launch. | | @types/react / @types/react-dom | 19.0.0 | 19.1.17 / 19.1.11 | match React 19.1 | | react-native-gesture-handler | 2.25.0 | 2.28.0 | 2.28 is the first 2.x with official 0.81 support (2.25's table stops at 0.79). Stays on 2.x; v3 drops the old architecture. | | @amplitude/analytics-react-native | 1.4.11 | 1.4.14 | Kotlin 2.1 (RN 0.80+) turns the deprecated `toUpperCase(Locale)` in 1.4.11 into a compile error | | tiktok-opensdk-react-native | ^0.10.7 | 0.10.7 (pinned) | it now carries a patch. 0.10.9 fixes the Android signature, but drops the `handleOpenURL`/`handleUserActivity` declarations that SceneDelegate imports | Patches added (`patches/`): - `react-native-track-player+4.1.2.patch`: Kotlin 2.1 rejects `Arguments.fromBundle(Bundle?)`. Now resolves `null` when there is no item. 4.1.2 is the last 4.x and has no fixed release. - `tiktok-opensdk-react-native+0.10.7.patch`: `onNewIntent(intent: Intent)` (RN 0.80 made `ActivityEventListener` non-null) and `reactApplicationContext.currentActivity`. Checked against 0.81 on the old architecture and left alone: Reanimated 3.19.5 (the official table lists 3.19.x for 0.78–0.81 on Paper; Reanimated 4 is out of scope), track-player 4.1.2 (patched above), collapsible-tab-view 8.0.1 (JS only), screens 4.18.0 (0.81 support since 4.14; `react-native-screens+4.18.0.patch` still applies; 4.25+ drops the old arch), pager-view 6.7.1 (7+ drops the old arch), video 6.18.0, svg 15.15.0 (0.81 support since 15.12.1), google-cast 4.6.2 (5.x is new-arch only), notifications 5.1.0, code-push 12.3.2 (README lists 0.77–0.86; 13.x needs a new OTA history), bootsplash 6.3.11, safe-area-context 5.6.2, keyboard-controller 1.19.0, flash-list 1.8.3, datetimepicker 8.3.0 (builds against 0.81.6). ## Template changes (rn-diff-purge 0.79.5 → 0.81.6) - Android: Kotlin 2.0.21 → 2.1.20, Gradle 8.13 → 8.14.3, new `gradlew`/`gradlew.bat`/wrapper jar, `MainApplication` uses `loadReactNative(this)` in place of `SoLoader.init` plus `load()`, and `edgeToEdgeEnabled=false` is added as in the template. The app already draws edge-to-edge through react-native-bars and the SDK 36 target, so this flag changes nothing. - AGP comes from RN: 8.8 → 8.11.0, which supports compileSdk 36, so `android.suppressUnsupportedCompileSdk=36` is removed. - `settings.gradle` resolves `@react-native/gradle-plugin` through `require.resolve` from `react-native`, not a hard-coded hoisted path. With this lockfile npm nests it under `node_modules/react-native/node_modules`. - Kept: `newArchEnabled=false`, the `AudiusScrypt` module (`ScryptPackage` is still registered), the debug manifest (it adds `SYSTEM_ALERT_WINDOW`), and `android:enableOnBackInvokedCallback="false"`. - **Back handling:** since 0.80, `ReactActivity` registers an `OnBackPressedCallback` when targetSdk ≥ 36, which calls `onBackPressed()` → JS `BackHandler`. Our `invokeDefaultOnBackPressed` override (`moveTaskToBack`) still runs. The manifest opt-out is kept as asked and can probably go in a later release. - iOS: no template change between 0.79.5 and 0.81.6. `Info.plist` and `RichPushExtension/Info.plist` now set `RCTNewArchEnabled=false`, which RN 0.80+ reads **at runtime**; a missing key means new arch. `pod install` writes the key and re-sorts the plist each time, so the sorted version is committed. ## AppDelegate / UIScene No code change needed. `RCTAppDelegate` is marked deprecated in 0.81 in favour of `RCTReactNativeFactory`, so there is a `-Wdeprecated-declarations` warning, but every API we use is unchanged: `automaticallyLoadReactNativeWindow`, `rootViewFactory`, `createRootViewController`, `setRootView:toRootViewController:`, `customizeRootView:`, `bundleURL`, and `dependencyProvider`. `didFinishLaunching` still creates the factory and skips the window. Moving to `RCTReactNativeFactory` can wait for the 0.82+ phase. Verified on a Release build: cold launch, a deep link while killed, and a deep link while backgrounded, on iOS 26.5 and iOS 27. iOS 27 needs UIScene and showed no launch crash. ## Web / React 19.1 - Typecheck: web, common and harmony are clean, and so is mobile. - `vite build` (prod env): succeeds. - web vitest: 178 passed, 9 skipped. `TrackTile › Public Premium (non-owner)` timed out once while the machine was under heavy load, then passed 3 out of 3 when run alone. ## Verification matrix (before the rebase; see the bottom for the rebased stack) | | iOS 26.5 sim (Release) | iOS 27 sim (Release) | Android API 36 emu (prodRelease, arm64) | |---|---|---|---| | Build | ✅ Xcode 27, `generic/platform=iOS Simulator` | same binary | ✅ debug keystore | | Old arch at runtime | ✅ JS thread `com.facebook.react.JavaScript` | – | ✅ `ReactRootView` + "Legacy Architecture" warning | | Launch past splash | ✅ | ✅ | ✅ `assets://index.android.bundle` | | Sign-up / sign-in screens | (signed in) | ✅ | ✅ including Create Password | | Signed-in feed, scroll | ✅ | – | – | | Playback + now playing drawer (open, drag closed) | ✅ | – | – | | Background → foreground while playing | ✅ audio kept going (0:09 → 0:41) | – | – | | Search | ✅ | – | – | | Profile: collapsible header + tab swipe | ✅ | – | – | | Deep link, warm | ✅ `audius://audius` | ✅ link delivered | – | | Deep link, cold | ✅ `audius://deadmau5` | ✅ (signed out → sign-on) | – | | Hardware Back | – | – | ✅ pops Create Password; at root backgrounds the app without killing it | | Sign-on footer above nav bar | – | – | ✅ | | Red JS errors | none, apart from a recurring `JSON Parse error: Unexpected character: <` (not yet traced) | none | none | The CodePush folder was moved aside or cleared before every cold launch, so all results above are from the bundled JS. ## Still needs a real device - background audio with the screen locked for more than 2 minutes - lock-screen and notification controls - Chromecast - push notifications ## Notes - A Release build that downloads a production OTA built for 1.5.186 or 1.5.187 runs a 0.79 bundle on a 0.81 binary. 1.5.188 ships with its own package.json version (#14654) so it never receives those OTAs. - The Hermes `ExceptionsManager` fix now applies through patch-package (#14643), renamed for 0.81.6 above. ## Verification of the rebased stack (2026-10-08) Built from a local, unpushed integration branch: #14652's tip (which contains #14647) merged with #14651, on top of #14654. | Check | Result | |---|---| | `npx tsc --noEmit -p packages/mobile` | pass | | eslint on the touched files / `turbo run verify` lint | pass (1 existing warning in `HostRemixContestDrawer.tsx`) | | `cd packages/mobile && npm test` | 9 suites, 34 tests pass | | iOS Release, `generic/platform=iOS Simulator`, arm64, `ENVFILE=.env.prod` | builds; Info.plist 1.1.201, `RCTNewArchEnabled=false`; bundle carries 1.5.188 | | Android `assembleProdRelease`, arm64-v8a, debug keystore | builds; versionName 1.1.537 | | iOS 26.5 sim, installed over the signed-in app, CodePush moved aside | launch to feed; JS thread `com.facebook.react.JavaScript` (old arch) | | Playback from the 2nd feed track | plays; play bar tracker moves | | Auto-advance | seek to 2:43/2:45 on track 2, advanced to track 3 | | Now playing drawer | opens, scrubber advances, tap-to-seek works | | Profile tabs | swipe Tracks → Albums, tap Reposts | | notifee/RNFB APNs token | a temporary console log (not committed, rebuilt without it afterwards) printed an 80-byte lowercase APNs token at startup | | Android 16 emulator, `pm clear` | `Loading JS bundle from "assets://index.android.bundle"`, "Legacy Architecture" warning, sign-up and sign-in screens render | | Android hardware Back | **not verified**: the emulator's system_server was killed by its watchdog three times under host load, so Back never got a clean run | | JS errors on iOS | only `Could not cache profile images` (content node timeout) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the patch does
#13609 added a fix to React Native's
Libraries/Core/ExceptionsManager.js. It adds agetErrorStackSafe()helper that reads.stackinside a try/catch, and returns it only when it's a string. The helper replaces the three places where the exception handler reads.stackdirectly:parseErrorStack(e?.stack)inreportExceptionrawStack: e.stackinreportExceptionif (firstArg?.stack)inreactConsoleErrorHandlerOn Hermes, reading
.stackon a value that inherits fromErrorin the legacy way (Foo.prototype = new Error()) throwsError.stack getter called with an invalid receiver. When that happens inside the global exception handler, the original error gets lost and the handler throws again. See react/react-native#43636 and facebook/hermes#1496.Why it never applied
packages/mobile/scripts/patch-react-native-exceptions.jslooked forpackages/mobile/node_modules/react-native/.... npm hoists react-native to the rootnode_modules(it's in the root ofpackage-lock.jsonon both 0.79.5 and 0.81.6). When the file wasn't found, the script ranprocess.exit(0), so postinstall printed "Patching React Native ExceptionsManager" and changed nothing. On main the installedExceptionsManager.jsis byte-identical to the npm tarball, so this fix has never shipped.Fix
patches/react-native+0.79.5.patch.scripts/postinstall.sh.scripts/patch-package.shalready runs root patches with--error-on-fail. So if react-native stops being installed at the root, or an RN upgrade changes the lines being replaced,npm install/ postinstall now fails instead of silently doing nothing. The diff is identical to the old script's output, generated from a pristine 0.79.5 tarball.RN 0.81 (#14642)
All four anchor lines are unchanged in 0.81.6. This patch applies cleanly to 0.81.6 (output byte-identical to a patch generated against 0.81.6), with patch-package's version-mismatch warning only. The upgrade PR should rename it to
react-native+0.81.6.patchonce this lands.Verification
node_modules/bash scripts/patch-package.shin a worktree with clonednode_modules:@react-native/babel-preset; the output contains the helper and its 3 call sites.Behavior change to note: this is the first time the fix ships.
rawStackand thefirstArgcheck now treat a non-string.stackas absent. Before, they passed it through or treated it as truthy.🤖 Generated with Claude Code