can1357/oh-my-pi · error · Error

No API key for

Error message

No API key for ${model.provider}

What it means

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.

Solutions

  1. Configure auth for the model's provider, then retry the handoff
  2. Switch to a model on an authenticated provider before compacting/handing off
  3. Pre-check getApiKey(model, sessionId) before starting handoff and fall back to another compaction method
  4. Verify env/config still holds the key if this previously worked

Example fix

// before
await handoff.generateDocument(signal); // throws 'No API key for anthropic'
// after
const key = await registry.getApiKey(model, sessionId);
if (!key) return fallbackCompaction();
await handoff.generateDocument(signal);
Defensive patterns

Strategy: validation

Validate before calling

const model = session.model();
if (!model) throw new Error("Select a model first");
const apiKey = await registry.getApiKey(model, session.sessionId());
if (!apiKey) throw new Error(`Configure auth for ${model.provider} before handoff`);

Try / catch

try {
  await handoff.generateDocument(signal);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("No API key for")) {
    // configure provider auth or switch models, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: generateDocument called when the current session model's provider has no resolvable API key for this session.

Common situations: 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.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/13058bd4834ef58d. Report an issue: GitHub.

Appendix: source

Thrown at packages/coding-agent/src/session/session-handoff.ts:123

			}
		};
		if (sourceSignal) {
			sourceSignal.addEventListener("abort", onSourceAbort, { once: true });
			if (sourceSignal.aborted) {
				onSourceAbort();
			}
		}

		try {
			throwIfHandoffAborted(handoffSignal);

			const model = this.#host.model();
			if (!model) {
				throw new Error("No model selected for handoff");
			}
			const apiKey = await this.#host.modelRegistry.getApiKey(model, this.#host.sessionId());
			if (!apiKey) {
				throw new Error(`No API key for ${model.provider}`);
			}

			// Build the handoff request through the SAME pipeline a live turn uses
			// (`runEphemeralTurn` / `/btw` share it) so the oneshot reads the
			// provider prompt cache the main turn populated instead of cold-missing
			// the whole prefix: identical system prompt, normalized tools, and
			// transform-/obfuscation-matched message history via
			// `convertMessagesToLlm` + `buildSideRequestContext`, plus the live turn's
			// effective provider cache key with a unique side `sessionId` so
			// OpenAI/Codex append-only state never mixes with the live turn.
			const cacheSessionId = this.#host.sessionId();
			// The loop sends `promptCacheKey` (providerPromptCacheKey) and falls back to
			// the provider session id; providers route on `promptCacheKey ?? sessionId`.
			// Both can diverge from this.#host.sessionId() (tan/subagent/shared sessions), so
			// mirror exactly what the live turn populated the cache under.
			const handoffPromptCacheKey = this.#host.agent.promptCacheKey ?? this.#host.agent.sessionId;
			const handoffPromptText = renderHandoffPrompt(this.#host.obfuscateTextForProvider(customInstructions));
			const handoffSnapshot: AgentMessage[] = [

View on GitHub (pinned to 9690622007)