From d94728aa7ebe941d72a6d84f4410242fad12bad8 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 17 Mar 2026 09:49:09 -0400 Subject: [PATCH] fix: prevent crash when cancelling OAuth provider login dialog (#821) (#831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OAuthSelectorComponent calls its onSelect callback synchronously (no await), but the callback was async — calling showLoginDialog which throws 'Login cancelled' on Escape. The unhandled rejection bubbled up to the uncaughtException handler and crashed GSD. Wrap the async work in a named function with .catch() so cancellation errors are swallowed gracefully. showLoginDialog already handles its own error display internally. --- .../src/modes/interactive/interactive-mode.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts index 39b867c00..9a01cc926 100644 --- a/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts @@ -3883,12 +3883,16 @@ export class InteractiveMode { const selector = new OAuthSelectorComponent( mode, this.session.modelRegistry.authStorage, - async (providerId: string) => { + (providerId: string) => { done(); - if (mode === "login") { - await this.showLoginDialog(providerId); - } else { + // OAuthSelectorComponent calls this synchronously (no await), + // so we must catch async errors here to prevent unhandled rejections + // when the user cancels the login dialog (#821). + const handleAsync = async () => { + if (mode === "login") { + await this.showLoginDialog(providerId); + } else { // Logout flow const providerInfo = this.session.modelRegistry.authStorage .getOAuthProviders() @@ -3919,6 +3923,11 @@ export class InteractiveMode { this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`); } } + }; + handleAsync().catch(() => { + // Swallow — showLoginDialog already handles its own errors. + // This prevents unhandled rejections when login is cancelled. + }); }, () => { done();