Summary
After upgrading a test device from iOS 26.4.1 to iOS 26.5 (23F77), our React Native 0.85.3 app crashes at launch with LLVM ERROR: OOM in Hermes' GC. The faulting thread is the Hermes JS runtime, but the proximate cause is the iOS main thread: Fabric's first mounting transaction calls RCTGetBorderImage thousands of times while the JS bundle is still being evaluated, producing ~18,902 CG raster regions / 6.9 GB of CG raster data before bundle eval finishes. Total process VM reaches ~8.8 GB, then Hermes' next OldGen allocation aborts the process (signal 6).
The same Release build on iOS 26.4.1 boots and runs normally; on iOS 26.5 the same binary aborts before any onboarding view renders. We worked around the bug by rewriting the offending background component in @shopify/react-native-skia (Skia draws to a single GPU surface and never calls RCTGetBorderImage); the production app now boots cleanly on iOS 26.5. The underlying RN + iOS interaction is filed here so other teams hitting it can find this thread and so RN can consider a defensive cache cap in RCTBorderDrawing.
Reproducer
https://github.com/Bodholdt-Speed-Testing/ios26-5-fabric-oom-repro
Important caveat: the reproducer scaffold isolates the structural pattern we believe is the trigger (5+ <Animated.View> children with borderRadius + borderWidth + JS-driver animated transform: [{ scale: interpolate(...) }] + animated opacity) but does not crash standalone on the affected device. We ran two cycles (5+30 views, then 15+60 views + 20 cards + 3 hidden Modals + header chrome). Both booted cleanly on iPhone 17 Pro Max running iOS 26.5 (23F77).
The crashing production app renders the same pattern but inside a much larger mount context: @shopify/react-native-skia initialized, react-native-reanimated@4.3.1 runtime active, a 4.5 MiB Hermes JS bundle evaluating, AsyncStorage + Keychain native calls at startup, multiple authentication/onboarding screens already mounted. In that surrounding VM pressure, the bug reliably reproduces 100% of the time.
We have not yet been able to compress that combined pressure into a minimal pure-RN reproducer. We're filing now with the scaffold + full diagnosis rather than blocking on extracting a smaller repro — happy to extract a stripped fork of the production app if it would help upstream diagnosis.
React Native version
System:
OS: macOS 26.5 (25F71)
Packages:
react-native: 0.85.3
react: 19.2.3
react-native-reanimated: 4.3.1
@shopify/react-native-skia: 2.6.2
Pods:
hermes-engine: 250829098.0.10
React-Fabric: bb0baa33d91839631d315800eb23e9aaa4338a44
React-FabricComponents: c504d0b0e2f3054b2ba19af839f175cb361153c0
React-FabricImage: 91eaea1cc58d25ae2596a9277bcfe028f92374c3
New Architecture: enabled (default for 0.85.3; Bridgeless + Fabric)
Device: iPhone 17 Pro Max
Device iOS: 26.5 (23F77) ← regression starts here
Last-known-good iOS: 26.4.1
Xcode: 26.x
Steps to reproduce (against the production app)
- Build the production app in Release configuration on an iPhone 17 Pro Max running iOS 26.5 (23F77).
- Install and launch.
- App aborts during JS bundle evaluation. Crash is 100% repeatable across uninstall + reinstall + reboot, fresh
pod install, and clean DerivedData.
The same binary on iOS 26.4.1 boots and runs normally end-to-end. The same source on an iOS 26.5 simulator does not crash (only the Release-on-device path reproduces).
Console output at crash
W ... ReactInstance: evaluateJavaScript() with JS bundle
LLVM ERROR: OOM: error_code(value = 12, ...message = Cannot allocate memory)
App terminated due to signal 6.
The LLVM ERROR is Hermes' embedded llvh::report_fatal_error printer (not the LLVM JIT) — there is no JavaScript exception. A global ErrorUtils.setGlobalHandler cannot catch this; we tried, and Hermes does not get the chance to install handlers before the OOM.
Crash report — key threads
Faulting thread (Hermes JS runtime):
abort
hermesvm::hermesFatalErrorHandler
llvh::report_fatal_error
GCBase::oom
HadesGC::OldGen::allocSlow
HadesGC::youngGenCollection
Interpreter::interpretFunction
TimerManager::attachGlobals (lambda)
Main thread (UI / Fabric mounting):
argb32_mark_constmask
RIPLayerBltShape
ripc_Render
ripc_DrawPath
CG::DisplayList::executeEntries
-[UIGraphicsImageRenderer imageWithActions:]
RCTGetBorderImage
RCTAddContourEffectToLayer
-[RCTViewComponentView finalizeUpdates:]
-[RCTMountingManager performTransaction:]
So Fabric's first mounting transaction is allocating CG image surfaces via RCTGetBorderImage → UIGraphicsImageRenderer for our initial view tree, the resulting raster blows the process VM budget, and Hermes — still busy evaluating the JS bundle on a separate thread — fails its next OldGen allocation and aborts.
vmSummary line at crash
CG raster data 6.9 GB 18,902 regions
(Out of ~8.8 GB total VM at the moment of abort.) 18,902 raster regions is roughly 200–500× what we would expect for this screen's view tree.
Render-only bisect on the production app
Each cycle is a full Release build + uninstall + reinstall + Trust + console launch on the same iPhone 17 Pro Max running iOS 26.5. We progressively stripped the failing screen (SpeedTestScreen):
| Cycle |
App state |
Result |
| 1 |
3 always-mounted modals stripped, body intact |
crashes (std::bad_alloc — signature changes from LLVM OOM) |
| 2 |
+ full ScrollView body + post-ScrollView overlays stripped |
crashes (LLVM ERROR: OOM) |
| 3 |
+ theme background + header chrome stripped (only Animated.View wrapper) |
boots cleanly |
| 4 |
Theme background re-enabled, header still stripped |
crashes (std::bad_alloc) |
| 5 |
Same as cycle 4 but outer Animated.View swapped for plain <View> |
crashes (std::bad_alloc) |
Cycle 5 specifically rules out the outer animated wrapper as the trigger. The trigger is the background subtree itself — 5 rings + 30 streaks, each <Animated.View> with all of:
borderRadius (5 rings: 90; 30 streaks: width/2)
borderWidth (rings: 2.5; streaks: 1)
- Animated
transform: [{ scale: ring.scale.interpolate([0,1], [0.05, 3.0]) }] (rings; useNativeDriver: false)
- Animated
opacity (rings: useNativeDriver: false; streaks: useNativeDriver: true)
Workarounds tried
- Clean rebuild + wipe DerivedData + fresh
pod install: no change.
- Reboot of device: no change.
- Revert all in-flight defensive try/catch wraps: no change. Confirms this is not a JS error our code is throwing.
RCT_NEW_ARCH_ENABLED=0 (try Legacy Renderer): blocked — react-native-reanimated@4.3.1's podspec aborts pod install with assert_new_architecture_enabled. Reanimated 4 hard-requires the New Architecture, and our app's animation layer depends on it. We cannot trial the Legacy Renderer as a diagnostic without ripping out Reanimated 4.
- A global
ErrorUtils.setGlobalHandler: prevented bundle eval from completing on Hermes Release builds (separate bug, off-topic here). Removed.
- Skia rewrite — works. We rewrote the offending background in
@shopify/react-native-skia (single <Canvas> root, <Circle style="stroke"> for rings, <RoundedRect> for streaks). Skia draws to one GPU surface and never calls RCTGetBorderImage. The production app now boots cleanly on iOS 26.5. This is a real workaround, but it's app-specific — RN itself remains exposed for any app rendering the trigger pattern on iOS 26.5.
Related code-area work
facebook/react-native#56915 — "Fix percentage-based border radius" — landed 2026-05-22, post-0.85.3, touches the same isUniform path in RCTBorderDrawing. No memory mention in that PR, but it is the closest recent change we can find in the relevant code area.
Asks
- Is this a known regression upstream on iOS 26.5? Public searches across GitHub issues, Apple Developer Forums, Stack Overflow, Reddit, and MacRumors turned up nothing matching this stack — happy to be pointed at a duplicate.
- Would a defensive cache cap in
RCTBorderDrawing (e.g., LRU bound on RCTGetBorderImage's cache) be considered as a temporary mitigation while Apple addresses the underlying CG raster regression?
- Are there any styling patterns known to inflate
RCTGetBorderImage raster region count on iOS 26.5 (e.g., percentage borderRadius, gradient borders, dashed borders, specific borderRadius/borderWidth combinations)?
We are happy to provide the full .ips file privately, run further on-device diagnostics, test a patched Hermes / Fabric build, or extract a more minimal reproducer from the production app on request. The reproduction is 100% reliable on our side.
cc @react-native-bot Platform: iOS
Summary
After upgrading a test device from iOS 26.4.1 to iOS 26.5 (23F77), our React Native 0.85.3 app crashes at launch with
LLVM ERROR: OOMin Hermes' GC. The faulting thread is the Hermes JS runtime, but the proximate cause is the iOS main thread: Fabric's first mounting transaction callsRCTGetBorderImagethousands of times while the JS bundle is still being evaluated, producing ~18,902 CG raster regions / 6.9 GB of CG raster data before bundle eval finishes. Total process VM reaches ~8.8 GB, then Hermes' next OldGen allocation aborts the process (signal 6).The same Release build on iOS 26.4.1 boots and runs normally; on iOS 26.5 the same binary aborts before any onboarding view renders. We worked around the bug by rewriting the offending background component in
@shopify/react-native-skia(Skia draws to a single GPU surface and never callsRCTGetBorderImage); the production app now boots cleanly on iOS 26.5. The underlying RN + iOS interaction is filed here so other teams hitting it can find this thread and so RN can consider a defensive cache cap inRCTBorderDrawing.Reproducer
https://github.com/Bodholdt-Speed-Testing/ios26-5-fabric-oom-repro
Important caveat: the reproducer scaffold isolates the structural pattern we believe is the trigger (5+
<Animated.View>children withborderRadius+borderWidth+ JS-driver animatedtransform: [{ scale: interpolate(...) }]+ animated opacity) but does not crash standalone on the affected device. We ran two cycles (5+30 views, then 15+60 views + 20 cards + 3 hidden Modals + header chrome). Both booted cleanly on iPhone 17 Pro Max running iOS 26.5 (23F77).The crashing production app renders the same pattern but inside a much larger mount context:
@shopify/react-native-skiainitialized,react-native-reanimated@4.3.1runtime active, a 4.5 MiB Hermes JS bundle evaluating, AsyncStorage + Keychain native calls at startup, multiple authentication/onboarding screens already mounted. In that surrounding VM pressure, the bug reliably reproduces 100% of the time.We have not yet been able to compress that combined pressure into a minimal pure-RN reproducer. We're filing now with the scaffold + full diagnosis rather than blocking on extracting a smaller repro — happy to extract a stripped fork of the production app if it would help upstream diagnosis.
React Native version
Steps to reproduce (against the production app)
pod install, and clean DerivedData.The same binary on iOS 26.4.1 boots and runs normally end-to-end. The same source on an iOS 26.5 simulator does not crash (only the Release-on-device path reproduces).
Console output at crash
The
LLVM ERRORis Hermes' embeddedllvh::report_fatal_errorprinter (not the LLVM JIT) — there is no JavaScript exception. A globalErrorUtils.setGlobalHandlercannot catch this; we tried, and Hermes does not get the chance to install handlers before the OOM.Crash report — key threads
Faulting thread (Hermes JS runtime):
Main thread (UI / Fabric mounting):
So Fabric's first mounting transaction is allocating CG image surfaces via
RCTGetBorderImage→UIGraphicsImageRendererfor our initial view tree, the resulting raster blows the process VM budget, and Hermes — still busy evaluating the JS bundle on a separate thread — fails its next OldGen allocation and aborts.vmSummaryline at crash(Out of ~8.8 GB total VM at the moment of abort.) 18,902 raster regions is roughly 200–500× what we would expect for this screen's view tree.
Render-only bisect on the production app
Each cycle is a full Release build + uninstall + reinstall + Trust + console launch on the same iPhone 17 Pro Max running iOS 26.5. We progressively stripped the failing screen (
SpeedTestScreen):std::bad_alloc— signature changes fromLLVM OOM)LLVM ERROR: OOM)Animated.Viewwrapper)std::bad_alloc)Animated.Viewswapped for plain<View>std::bad_alloc)Cycle 5 specifically rules out the outer animated wrapper as the trigger. The trigger is the background subtree itself — 5 rings + 30 streaks, each
<Animated.View>with all of:borderRadius(5 rings:90; 30 streaks:width/2)borderWidth(rings:2.5; streaks:1)transform: [{ scale: ring.scale.interpolate([0,1], [0.05, 3.0]) }](rings;useNativeDriver: false)opacity(rings:useNativeDriver: false; streaks:useNativeDriver: true)Workarounds tried
pod install: no change.RCT_NEW_ARCH_ENABLED=0(try Legacy Renderer): blocked —react-native-reanimated@4.3.1's podspec abortspod installwithassert_new_architecture_enabled. Reanimated 4 hard-requires the New Architecture, and our app's animation layer depends on it. We cannot trial the Legacy Renderer as a diagnostic without ripping out Reanimated 4.ErrorUtils.setGlobalHandler: prevented bundle eval from completing on Hermes Release builds (separate bug, off-topic here). Removed.@shopify/react-native-skia(single<Canvas>root,<Circle style="stroke">for rings,<RoundedRect>for streaks). Skia draws to one GPU surface and never callsRCTGetBorderImage. The production app now boots cleanly on iOS 26.5. This is a real workaround, but it's app-specific — RN itself remains exposed for any app rendering the trigger pattern on iOS 26.5.Related code-area work
facebook/react-native#56915 — "Fix percentage-based border radius" — landed 2026-05-22, post-0.85.3, touches the same
isUniformpath inRCTBorderDrawing. No memory mention in that PR, but it is the closest recent change we can find in the relevant code area.Asks
RCTBorderDrawing(e.g., LRU bound onRCTGetBorderImage's cache) be considered as a temporary mitigation while Apple addresses the underlying CG raster regression?RCTGetBorderImageraster region count on iOS 26.5 (e.g., percentageborderRadius, gradient borders, dashed borders, specificborderRadius/borderWidthcombinations)?We are happy to provide the full .ips file privately, run further on-device diagnostics, test a patched Hermes / Fabric build, or extract a more minimal reproducer from the production app on request. The reproduction is 100% reliable on our side.
cc @react-native-bot Platform: iOS