{"record":{"id":"75ec34a398c705cc","repo":"can1357/oh-my-pi","slug":"no-api-key-for-nextmodel-provider-nextmodel-i","errorCode":null,"errorMessage":"No API key for ${nextModel.provider}/${nextModel.id}","messagePattern":"No API key for (.+?)/(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/model-controls.ts","lineNumber":464,"sourceCode":"\t\treturn { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };\n\t}\n\n\tasync #cycleAvailableModel(direction: \"forward\" | \"backward\"): Promise<ModelCycleResult | undefined> {\n\t\tconst previousEditMode = this.#host.resolveActiveEditMode();\n\t\tconst availableModels = this.#host.modelRegistry.getAvailable();\n\t\tif (availableModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.#model;\n\t\tlet currentIndex = availableModels.findIndex(m => modelsAreEqual(m, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = availableModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst nextModel = availableModels[nextIndex];\n\n\t\tconst apiKey = await this.#host.modelRegistry.getApiKey(nextModel, this.#host.sessionId());\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);\n\t\t}\n\n\t\tthis.#host.modelRegistry.clearSuppressedSelector(formatModelStringWithRouting(nextModel));\n\t\tthis.#host.clearActiveRetryFallback();\n\t\tawait this.#host.setModelWithProviderSessionReset(nextModel);\n\t\tthis.#host.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);\n\t\tthis.#host.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);\n\t\t// Re-apply the current thinking level (or auto) for the newly selected model\n\t\tthis.#reapplyThinkingLevel();\n\t\tawait this.#host.syncAfterModelChange(previousEditMode);\n\n\t\treturn { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };\n\t}\n\n\t/**\n\t * Get all available models with valid API keys, filtered by `enabledModels` when configured.\n\t * See {@link filterAvailableModelsByEnabledPatterns} for supported pattern forms and limitations.\n\t */","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/model-controls.ts#L446-L482","documentation":"#cycleAvailableModel advances to the next model in the available list and resolves an API key via getApiKey before committing the switch; if no key resolves it throws. Cycling must only land on models the session can actually run, so unauthenticated neighbors in the cycle list are rejected with this error.","triggerScenarios":"Calling cycleModel (forward/backward) where availableModels[nextIndex] has no API key for the current session.","commonSituations":"Tab/keyboard cycling through models spanning multiple providers where only some providers have keys; using a curated model list that includes unconfigured providers; key removed mid-session.","solutions":["Configure API keys for all providers present in the cycle list, or trim the list to authenticated providers","Manually select a specific authenticated model instead of cycling","Pre-filter the available model list with hasConfiguredAuth/getApiKey so cycles only include usable models","Catch the error in the cycle handler and skip to the next candidate"],"exampleFix":"// before\nawait controls.cycleModel(\"forward\"); // may land on unkeyed model\n// after\nconst usable = models.filter(m => host.modelRegistry.hasConfiguredAuth(m));\n// cycle within `usable` only","handlingStrategy":"fallback","validationCode":"const cycleModels = allModels.filter(m =>\n  registry.hasConfiguredAuth(m) || Boolean(await registry.getApiKey(m, sessionId)),\n);","typeGuard":null,"tryCatchPattern":"try {\n  await controls.cycleModel(\"forward\");\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"No API key for\")) {\n    await controls.cycleModel(\"forward\"); // skip unkeyed neighbor\n  } else throw err;\n}","preventionTips":["Build cycle lists from authenticated models only","Configure keys for every provider in your model roster","Handle the error by advancing to the next candidate","Show key status alongside cycle UI"],"tags":["api-key","auth","model-cycling","configuration"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}