From 7ea37cd0823680a95b090737468516d9666dc05b Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 3 Jun 2026 18:39:00 -0500 Subject: [PATCH] fix(desktop): stop validating provider keys in launch setup The launch provider setup screen rejected too many legitimate users: a live credential probe ("key rejected"), a post-save runtime check ("still cannot reach X"), and an 8-char minimum all gated progression. Corporate proxies, regional blocks, rate-limited/flaky probes, and self-hosted endpoints all tripped these. Now we just require a non-empty value and save it; a genuinely bad key surfaces later at chat time instead of blocking onboarding. --- .../components/desktop-onboarding-overlay.tsx | 6 +-- apps/desktop/src/store/onboarding.ts | 46 ++++++------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/components/desktop-onboarding-overlay.tsx b/apps/desktop/src/components/desktop-onboarding-overlay.tsx index 7d091ee59..2763b93b6 100644 --- a/apps/desktop/src/components/desktop-onboarding-overlay.tsx +++ b/apps/desktop/src/components/desktop-onboarding-overlay.tsx @@ -56,8 +56,6 @@ interface ApiKeyOption { short?: string } -const MIN_KEY_LENGTH = 8 - const API_KEY_OPTIONS: ApiKeyOption[] = [ { id: 'openrouter', @@ -418,7 +416,9 @@ function ApiKeyForm({ canGoBack, ctx }: { canGoBack: boolean; ctx: OnboardingCon const [error, setError] = useState(null) const isLocal = option.envKey === 'OPENAI_BASE_URL' - const canSave = value.trim().length >= (isLocal ? 1 : MIN_KEY_LENGTH) + // Only require a non-empty value — no length/format validation, so a short + // or unusual key can't block the user from continuing. + const canSave = value.trim().length >= 1 const submit = async () => { if (!canSave || saving) { diff --git a/apps/desktop/src/store/onboarding.ts b/apps/desktop/src/store/onboarding.ts index 7cc8031c4..75ebd5074 100644 --- a/apps/desktop/src/store/onboarding.ts +++ b/apps/desktop/src/store/onboarding.ts @@ -9,8 +9,7 @@ import { setEnvVar, setModelAssignment, startOAuthLogin, - submitOAuthCode, - validateProviderCredential + submitOAuthCode } from '@/hermes' import { evaluateRuntimeReadiness, type RuntimeReadinessResult } from '@/lib/runtime-readiness' import { notify, notifyError } from '@/store/notifications' @@ -253,12 +252,16 @@ async function completeWithModelConfirm( ctx: OnboardingContext, providerLabel: string, preferredSlugs: string[], - onFail: (reason: null | string) => void + onFail: (reason: null | string) => void, + // When true, a failing runtime check no longer blocks progression — the + // user is allowed through onboarding regardless. Used by the API-key path, + // where we intentionally don't validate the key (it blocked too many users). + ignoreRuntimeGate = false ) { await ctx.requestGateway('reload.env').catch(() => undefined) const runtime = await checkRuntime(ctx) - if (!runtime.ready) { + if (!runtime.ready && !ignoreRuntimeGate) { onFail(runtime.reason) return @@ -616,23 +619,13 @@ export async function saveOnboardingApiKey(envKey: string, value: string, label: return { ok: false, message: 'Enter a value first.' } } - // Live-probe the credential BEFORE persisting so a mistyped key never lands - // in .env. A rejected key (reachable && !ok) hard-blocks; an unreachable - // probe (offline / provider down) falls through and saves with the usual - // runtime check, so we don't strand offline users. - try { - const probe = await validateProviderCredential(envKey, trimmed) - if (!probe.ok && probe.reachable) { - return { ok: false, message: probe.message || `That ${label} key was rejected.` } - } - } catch { - // Validation endpoint unavailable — don't block; fall through to save. - } - + // No key validation here on purpose: we previously live-probed the key and + // hard-blocked on a runtime check after saving, which rejected too many + // legitimate users (corporate proxies, regional blocks, flaky/rate-limited + // provider probes, self-hosted endpoints). We now save the value as-is and + // let the user proceed; an actually-bad key surfaces later at chat time. try { await setEnvVar(envKey, trimmed) - let stillFailing = false - let runtimeFailure: null | string = null // For API-key flows we don't have a definitive provider id (the // user picked which API key they're entering, but the corresponding // backend slug — e.g. OPENROUTER_API_KEY → "openrouter" — is the @@ -640,19 +633,8 @@ export async function saveOnboardingApiKey(envKey: string, value: string, label: // fetchProviderDefaultModel falls back to the first authenticated // provider returned by /api/model/options if none match. const slugCandidates = [envKey.replace(/_API_KEY$/, '').toLowerCase(), label.toLowerCase()] - await completeWithModelConfirm(ctx, label, slugCandidates, reason => { - stillFailing = true - runtimeFailure = reason - }) - - if (stillFailing) { - const failureDetail = (runtimeFailure ?? '').trim() - - return { - ok: false, - message: failureDetail || `Saved, but Hermes still cannot reach ${label}. Double-check the value.` - } - } + // ignoreRuntimeGate=true: never block onboarding on the runtime check. + await completeWithModelConfirm(ctx, label, slugCandidates, () => undefined, true) return { ok: true } } catch (error) {