From d3df16b5d1aae1ff323e542f068d63bdca6fad4b Mon Sep 17 00:00:00 2001 From: SaladDay Date: Wed, 1 Jul 2026 15:18:53 +0000 Subject: [PATCH] fix(providers): don't clear managed OAuth binding on status-query failure useManagedAuth now exposes isStatusSuccess. The account-clear effect in the Codex/Copilot selector sections only unbinds when the status query has *successfully* loaded and the bound account is genuinely gone. Previously a failed or still-pending query yielded an empty accounts array, which silently cleared the selection; the subsequent save then dropped providerType/authBinding and turned a managed provider into a broken unbound config. Addresses review feedback on #3879. --- src/components/providers/forms/CodexOAuthSection.tsx | 10 +++++++--- src/components/providers/forms/CopilotAuthSection.tsx | 9 ++++++--- src/components/providers/forms/hooks/useManagedAuth.ts | 6 ++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/providers/forms/CodexOAuthSection.tsx b/src/components/providers/forms/CodexOAuthSection.tsx index 7546edb75..398062c74 100644 --- a/src/components/providers/forms/CodexOAuthSection.tsx +++ b/src/components/providers/forms/CodexOAuthSection.tsx @@ -68,7 +68,7 @@ export const CodexOAuthSection: React.FC = ({ const { accounts, defaultAccountId, - isLoadingStatus, + isStatusSuccess, hasAnyAccount, pollingState, deviceCode, @@ -97,11 +97,15 @@ export const CodexOAuthSection: React.FC = ({ }; React.useEffect(() => { + // Only clear a bound account when the status query has *successfully* + // loaded and the account is genuinely gone. On a failed/pending query + // `accounts` is an empty array, which must not silently unbind the + // provider's managed account (that would corrupt the saved config). if ( mode !== "select" || !selectedAccountId || !onAccountSelect || - isLoadingStatus + !isStatusSuccess ) { return; } @@ -109,7 +113,7 @@ export const CodexOAuthSection: React.FC = ({ if (!accounts.some((account) => account.id === selectedAccountId)) { onAccountSelect(null); } - }, [accounts, isLoadingStatus, mode, onAccountSelect, selectedAccountId]); + }, [accounts, isStatusSuccess, mode, onAccountSelect, selectedAccountId]); const handleRemoveAccount = (accountId: string, e: React.MouseEvent) => { e.stopPropagation(); diff --git a/src/components/providers/forms/CopilotAuthSection.tsx b/src/components/providers/forms/CopilotAuthSection.tsx index a75b614d3..70d256583 100644 --- a/src/components/providers/forms/CopilotAuthSection.tsx +++ b/src/components/providers/forms/CopilotAuthSection.tsx @@ -71,7 +71,7 @@ export const CopilotAuthSection: React.FC = ({ accounts, defaultAccountId, migrationError, - isLoadingStatus, + isStatusSuccess, hasAnyAccount, pollingState, deviceCode, @@ -102,11 +102,14 @@ export const CopilotAuthSection: React.FC = ({ }; React.useEffect(() => { + // Only clear a bound account once the status query has *successfully* + // loaded and the account is genuinely gone. A failed/pending query yields + // an empty `accounts` array, which must not silently unbind the provider. if ( mode !== "select" || !selectedAccountId || !onAccountSelect || - isLoadingStatus + !isStatusSuccess ) { return; } @@ -114,7 +117,7 @@ export const CopilotAuthSection: React.FC = ({ if (!accounts.some((account) => account.id === selectedAccountId)) { onAccountSelect(null); } - }, [accounts, isLoadingStatus, mode, onAccountSelect, selectedAccountId]); + }, [accounts, isStatusSuccess, mode, onAccountSelect, selectedAccountId]); // 处理移除账号 const handleRemoveAccount = (accountId: string, e: React.MouseEvent) => { diff --git a/src/components/providers/forms/hooks/useManagedAuth.ts b/src/components/providers/forms/hooks/useManagedAuth.ts index 86360b397..638bda143 100644 --- a/src/components/providers/forms/hooks/useManagedAuth.ts +++ b/src/components/providers/forms/hooks/useManagedAuth.ts @@ -30,6 +30,8 @@ export function useManagedAuth( const { data: authStatus, isLoading: isLoadingStatus, + isSuccess: isStatusSuccess, + isError: isStatusError, refetch: refetchStatus, } = useQuery({ queryKey, @@ -215,6 +217,10 @@ export function useManagedAuth( return { authStatus, isLoadingStatus, + // Distinguish "status loaded successfully" from "loading / failed" so + // callers don't treat a failed query's empty `accounts` as authoritative. + isStatusSuccess, + isStatusError, accounts, hasAnyAccount: accounts.length > 0, isAuthenticated: authStatus?.authenticated ?? false,