Skip to content

Fix: Unhandled completion handlers cause crashes when delegate is deallocated#233

Open
noah44846 wants to merge 2 commits intohotwired:mainfrom
noah44846:fix/webkit-completion-handler-nil-delegate
Open

Fix: Unhandled completion handlers cause crashes when delegate is deallocated#233
noah44846 wants to merge 2 commits intohotwired:mainfrom
noah44846:fix/webkit-completion-handler-nil-delegate

Conversation

@noah44846
Copy link

Hey! We're running a production app with quite a large user base and have been seeing a few rare but hard crashes traced back to these methods. After digging into the code I think I found the cause and put together a fix — happy to be corrected if I'm reading this wrong.

The issue

Several WKNavigationDelegate / WKUIDelegate methods pass their mandatory completion handlers through an optional delegate chain. If the delegate has been deallocated by the time WebKit fires the callback, the optional chain silently does nothing, the handler is never called, and WebKit raises NSInternalInconsistencyException — crashing the entire app.

Affected methods

ColdBootVisitdidReceiveAuthenticationChallenge

ColdBootVisit.delegate is a weak reference to Session. While Session strongly owns the WKWebView, but from what I gathered WebKit's network process can keep the web view alive independently during an in-flight request. If Session is torn down mid-cold-boot (e.g. during a navigator replacement), ColdBootVisit can outlive it just long enough for the challenge callback to arrive with a nil delegate.

Worth noting: this challenge apparently fires on the first HTTPS connection as part of standard TLS server trust evaluation, not just pages with HTTP auth (as I originally thought) — so the race can happen on any cold boot.

WKUIControllerrunJavaScriptConfirmPanelWithMessage and runJavaScriptAlertPanelWithMessage

WKUIController is strongly owned by Navigator via webkitUIDelegate, but WKWebView.uiDelegate holds it weakly. If Navigator is torn down while WebKit is simultaneously invoking one of these methods (WebKit temporarily retains the delegate during the call), WKUIController.delegate is nil, the alert is never presented, its actions never fire, and the handler is never called.

Crash reports

ColdBootVisit

OS Version:     iOS 26.3 (23D127)
Hardware Model: iPhone17,1

Exception Type:     EXC_CRASH (SIGABRT)
Termination Reason: SIGNAL 6

NSInternalInconsistencyException:
Completion handler passed to -[HotwireNative.ColdBootVisit
webView:didReceiveAuthenticationChallenge:completionHandler:] was not called

Thread 0 Crashed:
0   CoreFoundation       objc_exception_throw
1   libobjc.A.dylib      objc_exception_throw + 88
2   CoreFoundation
3   WebKit
...
7   libsystem_blocks     _Block_release + 236   ← handler released uncalled

WKUIController

OS Version:     iOS 26.3 (23D127)
Hardware Model: iPhone15,4

Exception Type:     EXC_CRASH (SIGABRT)
Termination Reason: SIGNAL 6

NSInternalInconsistencyException:
Completion handler passed to -[HotwireNative.WKUIController
webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:] was not called

Thread 0 Crashed:
0   CoreFoundation       objc_exception_throw
1   libobjc.A.dylib      objc_exception_throw + 88
2   CoreFoundation
3   WebKit
...
7   libsystem_blocks     _Block_release + 236   ← handler released uncalled
8   clubcorner
9   libswiftCore.dylib

The fix

The pattern is the same in all cases: guard on delegate and call the handler with a safe default if it's nil. For the auth challenge that's .performDefaultHandling, for the confirm panel false (mirrors the user cancelling), and for the alert panel just calling through. I also proactively fixed runJavaScriptAlertPanelWithMessage since it has the same pattern, even though we haven't seen it crash yet.

Noah Godel added 2 commits March 5, 2026 13:09
Guard against nil delegate in ColdBootVisit's authentication challenge
handler and WKUIController's alert/confirm panel handlers. When the
delegate is deallocated before WebKit fires the callback, the completion
handler was silently dropped, causing an NSInternalInconsistencyException
crash on the main thread.
@noah44846
Copy link
Author

Follow-up: We observed another crash where a user tapped a push notification while a JavaScript confirm dialog was visible. In our app, tapping a notification can replace the active navigator, which causes the previous one and its presented alert to disappear without any action being triggered — so the completion handler is never called.

One option we considered would be to store a reference to the pending completion handler on WKUIController and call it in deinit when the navigator is torn down. That would cover this specific case, but it wouldn't handle situations where the alert is dismissed while the navigator stays alive.

A more defensive fix would be a small private UIAlertController subclass that calls the completion handler with a default value in viewDidDisappear if no action was taken — covering all dismissal scenarios regardless of cause. 7e5945a is just a how we solved the Issue internally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant