From 7e54aad9e6bb6205918cb253d21fdfbd4a6b2ced Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:21:17 +0800 Subject: [PATCH] fix(providers): clear provider list when OpenClaw JSON is deleted (#526) --- .../services/providers/provider-service.ts | 39 ++++++++++++++++++- electron/utils/secure-storage.ts | 8 +++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/electron/services/providers/provider-service.ts b/electron/services/providers/provider-service.ts index 93af8d283..f24ca98c4 100644 --- a/electron/services/providers/provider-service.ts +++ b/electron/services/providers/provider-service.ts @@ -7,8 +7,10 @@ import type { ProviderConfig, ProviderDefinition, } from '../../shared/providers/types'; +import { BUILTIN_PROVIDER_TYPES } from '../../shared/providers/types'; import { ensureProviderStoreMigrated } from './provider-migration'; import { + deleteProviderAccount, getDefaultProviderAccountId, getProviderAccount, listProviderAccounts, @@ -26,6 +28,8 @@ import { setDefaultProvider, storeApiKey, } from '../../utils/secure-storage'; +import { getActiveOpenClawProviders } from '../../utils/openclaw-auth'; +import { getOpenClawProviderKeyForType } from '../../utils/provider-keys'; import type { ProviderWithKeyInfo } from '../../shared/providers/types'; import { logger } from '../../utils/logger'; @@ -56,7 +60,40 @@ export class ProviderService { async listAccounts(): Promise { await ensureProviderStoreMigrated(); - return listProviderAccounts(); + const accounts = await listProviderAccounts(); + + // Sync check: remove stale accounts whose provider no longer exists in + // OpenClaw JSON (e.g. user deleted openclaw.json manually). + if (accounts.length > 0) { + const activeProviders = await getActiveOpenClawProviders(); + const configMissing = activeProviders.size === 0; + const staleIds: string[] = []; + + for (const account of accounts) { + const isBuiltin = (BUILTIN_PROVIDER_TYPES as readonly string[]).includes(account.vendorId); + const openClawKey = getOpenClawProviderKeyForType(account.vendorId, account.id); + const isActive = + activeProviders.has(account.vendorId) || + activeProviders.has(account.id) || + activeProviders.has(openClawKey); + + // If openclaw.json is completely empty/missing, drop ALL accounts. + // Otherwise only drop non-builtin accounts that are not in the config. + if (configMissing || (!isBuiltin && !isActive)) { + staleIds.push(account.id); + } + } + + if (staleIds.length > 0) { + for (const id of staleIds) { + logger.info(`[provider-sync] Removing stale provider account "${id}" (no longer in OpenClaw config)`); + await deleteProviderAccount(id); + } + return accounts.filter((a) => !staleIds.includes(a.id)); + } + } + + return accounts; } async getAccount(accountId: string): Promise { diff --git a/electron/utils/secure-storage.ts b/electron/utils/secure-storage.ts index 025b958ae..5f7834209 100644 --- a/electron/utils/secure-storage.ts +++ b/electron/utils/secure-storage.ts @@ -267,19 +267,23 @@ export async function getAllProvidersWithKeyInfo(): Promise< const providers = await getAllProviders(); const results: Array = []; const activeOpenClawProviders = await getActiveOpenClawProviders(); + // When openclaw.json is deleted/missing, the active set is empty. + // In that case, all providers (including builtins) should be cleaned up. + const configMissing = activeOpenClawProviders.size === 0; for (const provider of providers) { // Sync check: If it's a custom/OAuth provider and it no longer exists in OpenClaw config // (e.g. wiped by Gateway due to missing plugin, or manually deleted by user) // we should remove it from ClawX UI to stay consistent. - const isBuiltin = BUILTIN_PROVIDER_TYPES.includes(provider.type); + const isBuiltin = (BUILTIN_PROVIDER_TYPES as readonly string[]).includes(provider.type); // For custom/ollama providers, the OpenClaw config key is derived as // "-" where suffix = first 8 chars of providerId with hyphens stripped. // e.g. provider.id "custom-a1b2c3d4-..." → strip hyphens → "customa1b2c3d4..." → slice(0,8) → "customa1" // → openClawKey = "custom-customa1" // This must match getOpenClawProviderKey() in ipc-handlers.ts exactly. const openClawKey = getOpenClawProviderKeyForType(provider.type, provider.id); - if (!isBuiltin && !activeOpenClawProviders.has(provider.type) && !activeOpenClawProviders.has(provider.id) && !activeOpenClawProviders.has(openClawKey)) { + const isActive = activeOpenClawProviders.has(provider.type) || activeOpenClawProviders.has(provider.id) || activeOpenClawProviders.has(openClawKey); + if (configMissing || (!isBuiltin && !isActive)) { console.log(`[Sync] Provider ${provider.id} (${provider.type}) missing from OpenClaw, dropping from ClawX UI`); await deleteProvider(provider.id); continue;