diff --git a/server/modules/providers/list/claude/claude-auth.provider.ts b/server/modules/providers/list/claude/claude-auth.provider.ts index c94fe1b4..68874b9d 100644 --- a/server/modules/providers/list/claude/claude-auth.provider.ts +++ b/server/modules/providers/list/claude/claude-auth.provider.ts @@ -16,6 +16,10 @@ type ClaudeCredentialsStatus = { error?: string; }; +const hasErrorCode = (error: unknown, code: string): boolean => ( + error instanceof Error && 'code' in error && error.code === code +); + export class ClaudeProviderAuth implements IProviderAuth { /** * Checks whether the Claude Code CLI is available on this host. @@ -77,6 +81,8 @@ export class ClaudeProviderAuth implements IProviderAuth { * Checks Claude credentials in the same priority order used by Claude Code. */ private async checkCredentials(): Promise { + const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.'; + if (process.env.ANTHROPIC_API_KEY?.trim()) { return { authenticated: true, email: 'API Key Auth', method: 'api_key' }; } @@ -110,15 +116,33 @@ export class ClaudeProviderAuth implements IProviderAuth { return { authenticated: false, - email, - method: 'credentials_file', - error: 'OAuth token has expired. Please re-authenticate with claude login', + email: null, + method: null, + error: 'Claude login has expired. Run claude /login again.', }; } - return { authenticated: false, email: null, method: null }; - } catch { - return { authenticated: false, email: null, method: null }; + return { + authenticated: false, + email: null, + method: null, + error: missingCredentialsError, + }; + } catch (error) { + let errorMessage = 'Unable to read Claude credentials. Run claude /login again.'; + + if (hasErrorCode(error, 'ENOENT')) { + errorMessage = missingCredentialsError; + } else if (error instanceof SyntaxError) { + errorMessage = 'Claude credentials are unreadable. Run claude /login again.'; + } + + return { + authenticated: false, + email: null, + method: null, + error: errorMessage, + }; } } } diff --git a/src/components/provider-auth/hooks/useProviderAuthStatus.ts b/src/components/provider-auth/hooks/useProviderAuthStatus.ts index 387d6327..9231e770 100644 --- a/src/components/provider-auth/hooks/useProviderAuthStatus.ts +++ b/src/components/provider-auth/hooks/useProviderAuthStatus.ts @@ -70,34 +70,39 @@ export function useProviderAuthStatus( })); }, []); - const checkProviderAuthStatus = useCallback(async (provider: LLMProvider) => { + const checkProviderAuthStatus = useCallback(async (provider: LLMProvider): Promise => { setProviderLoading(provider); try { const response = await authenticatedFetch(PROVIDER_AUTH_STATUS_ENDPOINTS[provider]); if (!response.ok) { - setProviderStatus(provider, { + const status: ProviderAuthStatus = { authenticated: false, email: null, method: null, loading: false, error: FALLBACK_STATUS_ERROR, - }); - return; + }; + setProviderStatus(provider, status); + return status; } const payload = (await response.json()) as ProviderAuthStatusApiResponse; - setProviderStatus(provider, toProviderAuthStatus(payload.data)); + const status = toProviderAuthStatus(payload.data); + setProviderStatus(provider, status); + return status; } catch (caughtError) { console.error(`Error checking ${provider} auth status:`, caughtError); - setProviderStatus(provider, { + const status: ProviderAuthStatus = { authenticated: false, email: null, method: null, loading: false, error: toErrorMessage(caughtError), - }); + }; + setProviderStatus(provider, status); + return status; } }, [setProviderLoading, setProviderStatus]); diff --git a/src/components/settings/hooks/useSettingsController.ts b/src/components/settings/hooks/useSettingsController.ts index 5463a5bb..c81a7c04 100644 --- a/src/components/settings/hooks/useSettingsController.ts +++ b/src/components/settings/hooks/useSettingsController.ts @@ -213,12 +213,19 @@ export function useSettingsController({ isOpen, initialTab }: UseSettingsControl }, []); const handleLoginComplete = useCallback((exitCode: number) => { - if (exitCode !== 0 || !loginProvider) { + if (!loginProvider) { return; } - setSaveStatus('success'); - void checkProviderAuthStatus(loginProvider); + void (async () => { + const authStatus = await checkProviderAuthStatus(loginProvider); + + if (exitCode !== 0) { + console.warn(`Login process exited with code ${exitCode}; refreshing auth status before setting save status.`); + } + + setSaveStatus(authStatus.authenticated ? 'success' : 'error'); + })(); }, [checkProviderAuthStatus, loginProvider]); const saveSettings = useCallback(async () => {