can1357/oh-my-pi · error · Error

No API key for ${model.provider}

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.

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)

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.