From 8f6ccef1363d6f8144191e953ba9bff1037201dd Mon Sep 17 00:00:00 2001 From: Manuel Lopez <30648939+malopezr7@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:46:43 +0200 Subject: [PATCH] fix(ios): don't read the expo-updates launch asset before the controller starts With expo-updates 57, EnabledAppController.launchAssetUrl() reads startupProcedure, an implicitly unwrapped optional that only start() sets. ExpoUpdatesStartup.prepareReactNative, ExpoHostRuntimeDelegate.bundleURL() and isBundleURLStable called it before start(), which crashed with EXC_BREAKPOINT. Check isStarted first. --- .changeset/expo-updates-57-launch-asset-before-start.md | 5 +++++ .../react-native-brownfield/ios/Expo/ExpoHostRuntime.swift | 4 +++- .../ios/Expo/ExpoUpdatesStartup.swift | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/expo-updates-57-launch-asset-before-start.md diff --git a/.changeset/expo-updates-57-launch-asset-before-start.md b/.changeset/expo-updates-57-launch-asset-before-start.md new file mode 100644 index 000000000..a45c029b1 --- /dev/null +++ b/.changeset/expo-updates-57-launch-asset-before-start.md @@ -0,0 +1,5 @@ +--- +'@callstack/react-native-brownfield': patch +--- + +Fix an iOS crash with expo-updates 57 when updates are enabled. The Expo runtime no longer asks the Updates controller for its launch asset before the controller has started. diff --git a/packages/react-native-brownfield/ios/Expo/ExpoHostRuntime.swift b/packages/react-native-brownfield/ios/Expo/ExpoHostRuntime.swift index 1cb1695c4..194fa7c95 100644 --- a/packages/react-native-brownfield/ios/Expo/ExpoHostRuntime.swift +++ b/packages/react-native-brownfield/ios/Expo/ExpoHostRuntime.swift @@ -299,6 +299,7 @@ class ExpoHostRuntimeDelegate: ExpoReactNativeFactoryDelegate { #if canImport(EXUpdates) if AppController.isInitialized(), AppController.sharedInstance.isActiveController, + AppController.sharedInstance.isStarted, let launchAssetURL = AppController.sharedInstance.launchAssetUrl() { return launchAssetURL @@ -343,7 +344,8 @@ class ExpoHostRuntimeDelegate: ExpoReactNativeFactoryDelegate { #if canImport(EXUpdates) guard AppController.isInitialized() else { return false } return !AppController.sharedInstance.isActiveController - || AppController.sharedInstance.launchAssetUrl() != nil + || (AppController.sharedInstance.isStarted + && AppController.sharedInstance.launchAssetUrl() != nil) #else return true #endif diff --git a/packages/react-native-brownfield/ios/Expo/ExpoUpdatesStartup.swift b/packages/react-native-brownfield/ios/Expo/ExpoUpdatesStartup.swift index b46fef25b..d8374f80f 100644 --- a/packages/react-native-brownfield/ios/Expo/ExpoUpdatesStartup.swift +++ b/packages/react-native-brownfield/ios/Expo/ExpoUpdatesStartup.swift @@ -16,7 +16,8 @@ final class ExpoUpdatesStartup: NSObject { AppController.initializeWithoutStarting() let controller = AppController.sharedInstance guard controller.isActiveController else { completion(); return } - if updatesReady || controller.launchAssetUrl() != nil { + // expo-updates 57 traps in launchAssetUrl() until start() sets its startup procedure. + if updatesReady || (controller.isStarted && controller.launchAssetUrl() != nil) { completion() return }