Package
@clerk/expo@3.6.5 (useSignInWithGoogle, iOS). The same code is present verbatim in
@clerk/expo-google-signin@1.0.1, so upgrading does not resolve it.
What happens
On a release iOS build, tapping "Continue with Google" never reaches Google. The promise
rejects with one of:
Unable to open Safari.The authorization attempt failed for an unknown reasonNo presenting view controller available
Retrying reproduces it every time within the same session. Sign in with Apple on the same
build is unaffected.
Cause
ios/ClerkGoogleSignInModule.swift resolves the presenting controller like this:
privatefunc getPresentingViewController()->UIViewController?{guardlet scene =UIApplication.shared.connectedScenes.first as?UIWindowScene,let window = scene.windows.first,let rootVC = window.rootViewController else{returnnil}...}connectedScenes is a Set and UIWindowScene.windows is unordered, so .first returns an
arbitrary element in both cases — neither checks activationState nor isKeyWindow. An app
with more than one window (splash, keyboard, an overlay from another module) can therefore
hand GIDSignIn a controller that is not in the visible hierarchy.
That produces the two observed outcomes:
- resolution returns
nil → the module's own "No presenting view controller available" - resolution returns an off-screen window →
GIDSignIn cannot present its authorization
session and AppAuth reports OIDErrorCodeSafariOpenError, surfaced through
handleSignInResult as error.localizedDescription → "Unable to open Safari."
Suggested fix
privatefunc getPresentingViewController()->UIViewController?{letscenes=UIApplication.shared.connectedScenes.compactMap{ $0 as?UIWindowScene}letscene= scenes.first{ $0.activationState ==.foregroundActive }?? scenes.first { $0.activationState ==.foregroundInactive }?? scenes.first
guardlet window = scene?.windows.first(where:{ $0.isKeyWindow })?? scene?.windows.first(where:{ !$0.isHidden && $0.rootViewController !=nil}),let rootVC = window.rootViewController else{returnnil}vartopVC= rootVC
whilelet presentedVC = topVC.presentedViewController, !presentedVC.isBeingDismissed {
topVC = presentedVC
}return topVC
}The isBeingDismissed guard covers a related case: a controller mid-dismissal is still
reachable through presentedViewController but can no longer present.
Environment
- Expo SDK 55, React Native 0.83.2, Hermes, Fabric
- iOS 26.5 / 26.6, physical devices and simulator
GoogleSignIn ~> 9.0 as declared by ClerkGoogleSignIn.podspec
Package
@clerk/expo@3.6.5(useSignInWithGoogle, iOS). The same code is present verbatim in@clerk/expo-google-signin@1.0.1, so upgrading does not resolve it.What happens
On a release iOS build, tapping "Continue with Google" never reaches Google. The promise
rejects with one of:
Unable to open Safari.The authorization attempt failed for an unknown reasonNo presenting view controller availableRetrying reproduces it every time within the same session. Sign in with Apple on the same
build is unaffected.
Cause
ios/ClerkGoogleSignInModule.swiftresolves the presenting controller like this:connectedScenesis aSetandUIWindowScene.windowsis unordered, so.firstreturns anarbitrary element in both cases — neither checks
activationStatenorisKeyWindow. An appwith more than one window (splash, keyboard, an overlay from another module) can therefore
hand
GIDSignIna controller that is not in the visible hierarchy.That produces the two observed outcomes:
nil→ the module's own"No presenting view controller available"GIDSignIncannot present its authorizationsession and AppAuth reports
OIDErrorCodeSafariOpenError, surfaced throughhandleSignInResultaserror.localizedDescription→"Unable to open Safari."Suggested fix
The
isBeingDismissedguard covers a related case: a controller mid-dismissal is stillreachable through
presentedViewControllerbut can no longer present.Environment
GoogleSignIn ~> 9.0as declared byClerkGoogleSignIn.podspec