From f47cce70f19ac9a078a8dcdeefac7bf7396ab921 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 11 Mar 2026 08:24:06 -0700 Subject: [PATCH] fix(ui): guard resumeOAuthFlow against wrong hook instance consuming OAuth result --- .../src/hooks/useUserMcpOAuthFlow.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx b/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx index 80f042bbfe9..c88281b770a 100644 --- a/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx +++ b/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx @@ -195,6 +195,19 @@ export const useUserMcpOAuthFlow = ({ const storedResult = getStorage(RESULT_KEY); if (!storedResult) return; + // When multiple OAuth2ConnectButton components are mounted (one per server + // card), each holds its own hook instance. All run resumeOAuthFlow() on + // mount and would compete for the same RESULT_KEY. Peek at the stored + // flow state first: only the hook instance whose serverId matches the one + // that initiated the OAuth flow should consume the result. + const rawFlowState = getStorage(FLOW_STATE_KEY); + if (rawFlowState) { + try { + const peeked = JSON.parse(rawFlowState) as StoredFlowState; + if (peeked.serverId && peeked.serverId !== serverId) return; + } catch (_) {} + } + processingRef.current = true; clearStorage(RESULT_KEY); @@ -259,7 +272,7 @@ export const useUserMcpOAuthFlow = ({ clearStorage(FLOW_STATE_KEY); setTimeout(() => { processingRef.current = false; }, 1000); } - }, [accessToken, onSuccess]); + }, [accessToken, serverId, onSuccess]); useEffect(() => { resumeOAuthFlow();