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.
This commit is contained in:
Brooklyn Nicholson
2026-06-03 18:39:00 -05:00
parent 1927ff217e
commit 7ea37cd082
2 changed files with 17 additions and 35 deletions

View File

@ -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 | string>(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) {

View File

@ -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) {