{"record":{"id":"7ee4d30d4f7016a8","repo":"can1357/oh-my-pi","slug":"unexpected-stop-no-api-key-for-model-provider","errorCode":null,"errorMessage":"unexpected-stop: no API key for ${model.provider}/${model.id}","messagePattern":"unexpected-stop: no API key for (.+?)/(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/unexpected-stop-classifier.ts","lineNumber":95,"sourceCode":"\t\treturn undefined;\n\t} catch (error) {\n\t\tlogger.debug(\"unexpected-stop: classification failed\", {\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\tbackend,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n\nasync function classifyOnline(text: string, deps: ClassifyUnexpectedStopDeps): Promise<boolean | undefined> {\n\tconst resolved = resolveRoleSelection([\"tiny\", \"smol\"], deps.settings, deps.registry.getAvailable());\n\tconst model = resolved?.model;\n\tif (!model) {\n\t\tthrow new Error(\"unexpected-stop: no tiny/smol model available for classification\");\n\t}\n\tconst apiKey = await deps.registry.getApiKey(model, deps.sessionId);\n\tif (!apiKey) {\n\t\tthrow new Error(`unexpected-stop: no API key for ${model.provider}/${model.id}`);\n\t}\n\tconst metadata = deps.metadataResolver?.(model.provider);\n\tconst maxTokens = ONLINE_REASONING_SAFE_MAX_TOKENS;\n\n\tconst response = await retryTransientCompletion(\n\t\t() =>\n\t\t\tcompleteSimple(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\tsystemPrompt: [CLASSIFIER_SYSTEM_PROMPT],\n\t\t\t\t\tmessages: [{ role: \"user\", content: text, timestamp: Date.now() }],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tapiKey: deps.registry.resolver(model, deps.sessionId),\n\t\t\t\t\tmaxTokens,\n\t\t\t\t\tdisableReasoning: true,\n\t\t\t\t\tmetadata,\n\t\t\t\t\tsignal: deps.signal,","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/unexpected-stop-classifier.ts#L77-L113","documentation":"After resolving a tiny/smol model for online stop classification, classifyOnline fetches an API key via registry.getApiKey. If none exists for that model's provider it throws this error naming provider/model. Classification cannot proceed without credentials.","triggerScenarios":"A tiny/smol model resolves (e.g. openai/gpt-4o-mini) but no API key is stored for that provider: env var unset, auth file missing the provider, or session-scoped key lookup fails.","commonSituations":"User authenticated their main provider but the small-model role points at a different provider; keys present locally but not in CI; key revoked/expired and removed from the registry.","solutions":["Add an API key for the named provider (env var or provider auth the registry reads).","Point the tiny/smol role at a model whose provider already has credentials.","Handle in classifyUnexpectedStop and fall back to local classification.","Re-authenticate with the provider if the stored key was revoked."],"exampleFix":"// before\n// smol role = openai/gpt-4o-mini but OPENAI_API_KEY unset\n// after\nexport OPENAI_API_KEY=sk-...  # or set smol role to a provider you have a key for","handlingStrategy":"validation","validationCode":"const model = resolveRoleSelection([\"tiny\", \"smol\"], settings, registry.getAvailable())?.model;\nif (model && !(await registry.getApiKey(model, sessionId))) {\n  throw new Error(`Set a key for ${model.provider} before enabling online classification`);\n}\n","typeGuard":null,"tryCatchPattern":"try {\n  verdict = await classifyUnexpectedStop(text, deps);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"unexpected-stop: no API key for\")) {\n    verdict = undefined; // degrade gracefully to local classification\n  } else throw err;\n}","preventionTips":["Keep keys configured for every provider any role (tiny/smol) may resolve to.","Point small-model roles at providers you already authenticate with.","Use the same secret source in CI as locally, or pass keys explicitly.","Handle undefined verdicts in callers so missing keys degrade, not crash."],"tags":["api-key","authentication","classification"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}