can1357/oh-my-pi · error

unexpected-stop: no API key for ${model.provider}/${model.id

Error message

unexpected-stop: no API key for ${model.provider}/${model.id}

What it means

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.

Source

Thrown at packages/coding-agent/src/session/unexpected-stop-classifier.ts:95

		return undefined;
	} catch (error) {
		logger.debug("unexpected-stop: classification failed", {
			error: error instanceof Error ? error.message : String(error),
			backend,
		});
		return undefined;
	}
}

async function classifyOnline(text: string, deps: ClassifyUnexpectedStopDeps): Promise<boolean | undefined> {
	const resolved = resolveRoleSelection(["tiny", "smol"], deps.settings, deps.registry.getAvailable());
	const model = resolved?.model;
	if (!model) {
		throw new Error("unexpected-stop: no tiny/smol model available for classification");
	}
	const apiKey = await deps.registry.getApiKey(model, deps.sessionId);
	if (!apiKey) {
		throw new Error(`unexpected-stop: no API key for ${model.provider}/${model.id}`);
	}
	const metadata = deps.metadataResolver?.(model.provider);
	const maxTokens = ONLINE_REASONING_SAFE_MAX_TOKENS;

	const response = await retryTransientCompletion(
		() =>
			completeSimple(
				model,
				{
					systemPrompt: [CLASSIFIER_SYSTEM_PROMPT],
					messages: [{ role: "user", content: text, timestamp: Date.now() }],
				},
				{
					apiKey: deps.registry.resolver(model, deps.sessionId),
					maxTokens,
					disableReasoning: true,
					metadata,
					signal: deps.signal,

View on GitHub (pinned to 9690622007)

Solutions

  1. Add an API key for the named provider (env var or provider auth the registry reads).
  2. Point the tiny/smol role at a model whose provider already has credentials.
  3. Handle in classifyUnexpectedStop and fall back to local classification.
  4. Re-authenticate with the provider if the stored key was revoked.

Example fix

// before
// smol role = openai/gpt-4o-mini but OPENAI_API_KEY unset
// after
export OPENAI_API_KEY=sk-...  # or set smol role to a provider you have a key for
Defensive patterns

Strategy: validation

Validate before calling

const model = resolveRoleSelection(["tiny", "smol"], settings, registry.getAvailable())?.model;
if (model && !(await registry.getApiKey(model, sessionId))) {
  throw new Error(`Set a key for ${model.provider} before enabling online classification`);
}

Try / catch

try {
  verdict = await classifyUnexpectedStop(text, deps);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("unexpected-stop: no API key for")) {
    verdict = undefined; // degrade gracefully to local classification
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

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

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/7ee4d30d4f7016a8. Report an issue: GitHub.