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.
This commit is contained in:
SaladDay
2026-07-01 15:18:53 +00:00
parent eca472dbf7
commit d3df16b5d1
3 changed files with 19 additions and 6 deletions
@@ -68,7 +68,7 @@ export const CodexOAuthSection: React.FC<CodexOAuthSectionProps> = ({
const {
accounts,
defaultAccountId,
isLoadingStatus,
isStatusSuccess,
hasAnyAccount,
pollingState,
deviceCode,
@@ -97,11 +97,15 @@ export const CodexOAuthSection: React.FC<CodexOAuthSectionProps> = ({
};
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<CodexOAuthSectionProps> = ({
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();
@@ -71,7 +71,7 @@ export const CopilotAuthSection: React.FC<CopilotAuthSectionProps> = ({
accounts,
defaultAccountId,
migrationError,
isLoadingStatus,
isStatusSuccess,
hasAnyAccount,
pollingState,
deviceCode,
@@ -102,11 +102,14 @@ export const CopilotAuthSection: React.FC<CopilotAuthSectionProps> = ({
};
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<CopilotAuthSectionProps> = ({
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) => {
@@ -30,6 +30,8 @@ export function useManagedAuth(
const {
data: authStatus,
isLoading: isLoadingStatus,
isSuccess: isStatusSuccess,
isError: isStatusError,
refetch: refetchStatus,
} = useQuery<ManagedAuthStatus>({
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,