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 reason
No 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:
private func getPresentingViewController() -> UIViewController? {
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let rootVC = window.rootViewController else {
return nil
}
...
}
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
private func getPresentingViewController() -> UIViewController? {
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
let scene = scenes.first { $0.activationState == .foregroundActive }
?? scenes.first { $0.activationState == .foregroundInactive }
?? scenes.first
guard let window = scene?.windows.first(where: { $0.isKeyWindow })
?? scene?.windows.first(where: { !$0.isHidden && $0.rootViewController != nil }),
let rootVC = window.rootViewController else {
return nil
}
var topVC = rootVC
while let 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