{"record":{"id":"13058bd4834ef58d","repo":"can1357/oh-my-pi","slug":"no-api-key-for-model-provider","errorCode":null,"errorMessage":"No API key for ${model.provider}","messagePattern":"No API key for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/session-handoff.ts","lineNumber":123,"sourceCode":"\t\t\t}\n\t\t};\n\t\tif (sourceSignal) {\n\t\t\tsourceSignal.addEventListener(\"abort\", onSourceAbort, { once: true });\n\t\t\tif (sourceSignal.aborted) {\n\t\t\t\tonSourceAbort();\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tthrowIfHandoffAborted(handoffSignal);\n\n\t\t\tconst model = this.#host.model();\n\t\t\tif (!model) {\n\t\t\t\tthrow new Error(\"No model selected for handoff\");\n\t\t\t}\n\t\t\tconst apiKey = await this.#host.modelRegistry.getApiKey(model, this.#host.sessionId());\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(`No API key for ${model.provider}`);\n\t\t\t}\n\n\t\t\t// Build the handoff request through the SAME pipeline a live turn uses\n\t\t\t// (`runEphemeralTurn` / `/btw` share it) so the oneshot reads the\n\t\t\t// provider prompt cache the main turn populated instead of cold-missing\n\t\t\t// the whole prefix: identical system prompt, normalized tools, and\n\t\t\t// transform-/obfuscation-matched message history via\n\t\t\t// `convertMessagesToLlm` + `buildSideRequestContext`, plus the live turn's\n\t\t\t// effective provider cache key with a unique side `sessionId` so\n\t\t\t// OpenAI/Codex append-only state never mixes with the live turn.\n\t\t\tconst cacheSessionId = this.#host.sessionId();\n\t\t\t// The loop sends `promptCacheKey` (providerPromptCacheKey) and falls back to\n\t\t\t// the provider session id; providers route on `promptCacheKey ?? sessionId`.\n\t\t\t// Both can diverge from this.#host.sessionId() (tan/subagent/shared sessions), so\n\t\t\t// mirror exactly what the live turn populated the cache under.\n\t\t\tconst handoffPromptCacheKey = this.#host.agent.promptCacheKey ?? this.#host.agent.sessionId;\n\t\t\tconst handoffPromptText = renderHandoffPrompt(this.#host.obfuscateTextForProvider(customInstructions));\n\t\t\tconst handoffSnapshot: AgentMessage[] = [","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/session-handoff.ts#L105-L141","documentation":"After confirming a model is selected, generateDocument resolves its API key via modelRegistry.getApiKey(model, sessionId); if none resolves it throws. Like the model-switch guards, handoff refuses to run an LLM generation the session cannot authenticate, and this message reports only the provider name.","triggerScenarios":"generateDocument called when the current session model's provider has no resolvable API key for this session.","commonSituations":"Handoff/compaction triggered after credentials were removed or expired; model set on a provider never configured; key resolution depends on session context that is absent.","solutions":["Configure auth for the model's provider, then retry the handoff","Switch to a model on an authenticated provider before compacting/handing off","Pre-check getApiKey(model, sessionId) before starting handoff and fall back to another compaction method","Verify env/config still holds the key if this previously worked"],"exampleFix":"// before\nawait handoff.generateDocument(signal); // throws 'No API key for anthropic'\n// after\nconst key = await registry.getApiKey(model, sessionId);\nif (!key) return fallbackCompaction();\nawait handoff.generateDocument(signal);","handlingStrategy":"validation","validationCode":"const model = session.model();\nif (!model) throw new Error(\"Select a model first\");\nconst apiKey = await registry.getApiKey(model, session.sessionId());\nif (!apiKey) throw new Error(`Configure auth for ${model.provider} before handoff`);","typeGuard":null,"tryCatchPattern":"try {\n  await handoff.generateDocument(signal);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"No API key for\")) {\n    // configure provider auth or switch models, then retry\n  } else throw err;\n}","preventionTips":["Verify getApiKey resolves before starting handoff/compaction","Keep provider credentials configured and unexpired","Offer model switching on auth failure instead of failing compaction","Fall back to non-LLM compaction when no key is available"],"tags":["api-key","auth","handoff","compaction"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}