From 1661f09d0a10d5448f6eb6954a41de5b7969ad49 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 28 Aug 2026 04:53:47 -0400 Subject: [PATCH] fix(web): surface a failed OAuth clear instead of floating its rejection (#2144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot review round 10. Both Clear entry points discarded the promise with `void`, but `clearServerOAuthAndDisconnect` does not own its failures — the store write or the disconnect can reject. That produced an unhandled rejection and left the user with a control that had silently done nothing. A shared `runClear` terminates it with `.catch` and raises a red, non-expiring notification: the tokens may still be on disk and the session may still be up, which is not a notice to let time out. An RFC 7009 failure is not this case — those come back as an outcome and are already reported in the success toast. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall --- clients/web/src/App.tsx | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index ca01a0b8..b89b2e1d 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -1320,19 +1320,42 @@ function App() { // target isn't resolvable. const settingsModalIsStdio = settingsModalServerType === "stdio"; + /** + * Run a clear from a key/click handler, which cannot await. + * + * `clearServerOAuthAndDisconnect` does **not** own its failures — the store + * write or the disconnect can reject — so a bare `void` would produce an + * unhandled rejection and leave the user with a control that silently did + * nothing. (An RFC 7009 failure is not this: those come back as an outcome + * and are already reported in the success toast.) + */ + const runClear = useCallback( + (server: Parameters[0]) => { + clearServerOAuthAndDisconnect(server).catch((err: unknown) => { + notifications.show({ + title: "Could not clear the stored OAuth state", + message: err instanceof Error ? err.message : String(err), + color: "red", + // The tokens may still be on disk and the session may still be up, + // so this is not a notice to let time out. + autoClose: false, + }); + }); + }, + [clearServerOAuthAndDisconnect], + ); + const handleClearConnectionOAuth = useCallback(() => { if (!activeServer) return; - void clearServerOAuthAndDisconnect(activeServer); - }, [activeServer, clearServerOAuthAndDisconnect]); + runClear(activeServer); + }, [activeServer, runClear]); const handleClearStoredOAuthFromSettings = useCallback(() => { if (!settingsModalTarget) return; // Clear from *inside* the settings modal, so the draft is what the user is // looking at rather than what the debounced save has persisted (#2144). - void clearServerOAuthAndDisconnect( - serverWithDraftSettings(settingsModalTarget, settingsDraft), - ); - }, [settingsModalTarget, settingsDraft, clearServerOAuthAndDisconnect]); + runClear(serverWithDraftSettings(settingsModalTarget, settingsDraft)); + }, [settingsModalTarget, settingsDraft, runClear]); const onSettingsModalClose = useCallback(() => { flushSettingsDraft();