{"record":{"id":"5321851298cd1fe0","repo":"can1357/oh-my-pi","slug":"no-api-key-for-retry-fallback-selector-raw","errorCode":null,"errorMessage":"No API key for retry fallback ${selector.raw}","messagePattern":"No API key for retry fallback (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/turn-recovery.ts","lineNumber":1711,"sourceCode":"\t\t}\n\t}\n\n\tasync applyRetryFallbackCandidate(\n\t\trole: string,\n\t\tselector: RetryFallbackSelector,\n\t\tcurrentSelector: string,\n\t\toptions?: { pinFallback?: boolean; apiKey?: string; signal?: AbortSignal },\n\t): Promise<boolean> {\n\t\tconst resolved = resolveModelOverride([selector.raw], this.#host.modelRegistry, this.#host.settings);\n\t\tconst candidate = resolved.model ?? this.#host.modelRegistry.find(selector.provider, selector.id);\n\t\tif (!candidate) {\n\t\t\tthrow new Error(`Retry fallback model not found: ${selector.raw}`);\n\t\t}\n\t\tconst apiKey =\n\t\t\toptions?.apiKey ??\n\t\t\t(await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId(), { signal: options?.signal }));\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(`No API key for retry fallback ${selector.raw}`);\n\t\t}\n\t\tif (options?.signal?.aborted) return false;\n\n\t\t// Capture the configured selector (auto-aware) so a fallback chain preserves\n\t\t// `auto` instead of collapsing it to the level it resolved to this turn.\n\t\tconst currentThinkingLevel = this.#host.configuredThinkingLevel();\n\t\tconst requestedThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;\n\t\t// A fallback selector's explicit level (or the carried level after the\n\t\t// replacement model's floor clamp) must never exceed the session's\n\t\t// per-spawn effort ceiling.\n\t\tconst nextThinkingLevel =\n\t\t\trequestedThinkingLevel === AUTO_THINKING\n\t\t\t\t? requestedThinkingLevel\n\t\t\t\t: clampThinkingLevelToCeiling(candidate, requestedThinkingLevel, this.#host.thinkingLevelCeiling());\n\t\tconst candidateSelector = formatModelStringWithRouting(candidate);\n\t\tconst previousModel = this.#host.model();\n\t\t// Mark routing BEFORE the swap: `setModelWithProviderSessionReset` moves the\n\t\t// model and fans `model_changed` out to subscribers synchronously, and a","sourceCodeStart":1693,"sourceCodeEnd":1729,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/turn-recovery.ts#L1693-L1729","documentation":"After successfully resolving the fallback model, TurnRecovery asks the model registry for an API key (getApiKey). If no key is available for that model/provider it throws this error instead of attempting a doomed request. The key may come from the registry's own sources; options.apiKey bypasses the lookup.","triggerScenarios":"Retry-fallback invoked with a valid model but options.apiKey is undefined and the registry cannot find a key for the resolved candidate's provider for this session (no env var, no auth.json/credentials entry, no provider auth configured).","commonSituations":"Fallback chain crosses to a provider the user never authenticated with (e.g. primary is Anthropic, fallback is OpenAI but OPENAI_API_KEY unset); key was rotated/deleted; running in CI where env secrets are missing.","solutions":["Pass options.apiKey explicitly when invoking the fallback.","Configure a key for the fallback provider (env var, provider login, or auth config the registry reads).","Pick a fallback model whose provider already has a key configured.","Check getApiKey's key sources (env, config files) for the specific provider and add the missing entry."],"exampleFix":"// before\nawait recovery.retryWithFallback(selector, { pinFallback: true });\n// after\nawait recovery.retryWithFallback(selector, { pinFallback: true, apiKey: process.env.OPENAI_API_KEY });","handlingStrategy":"validation","validationCode":"const key = options.apiKey ?? (await registry.hasApiKey(candidate) ? undefined : await registry.getApiKey(candidate, sessionId));\nif (!key && !options.apiKey) throw new Error(`Configure a key for ${candidate.provider} before fallback`);\n","typeGuard":null,"tryCatchPattern":"try {\n  await recovery.retryWithFallback(selector, opts);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"No API key for retry fallback\")) {\n    const provider = selector.raw.split(\"/\")[0];\n    // prompt user / load from secret store for that provider\n  } else throw err;\n}","preventionTips":["Configure keys for every provider referenced in any fallback chain.","Pass options.apiKey explicitly in non-interactive environments (CI).","Pre-flight check key availability for all configured fallbacks at startup.","Re-authenticate providers after key rotation."],"tags":["api-key","authentication","configuration"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}