{"record":{"id":"ec37dc688ae555b7","repo":"earendil-works/pi","slug":"no-api-key-for-provider-provider","errorCode":null,"errorMessage":"No API key for provider: ${provider}","messagePattern":"No API key for provider: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/ai/src/api/anthropic-messages.ts","lineNumber":306,"sourceCode":"function hasHeader(headers: ProviderHeaders | undefined, name: string): boolean {\n\tif (!headers) return false;\n\tconst expected = name.toLowerCase();\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (key.toLowerCase() === expected && value !== null && value.trim().length > 0) return true;\n\t}\n\treturn false;\n}\n\nfunction assertRequestAuth(provider: string, apiKey: string | undefined, headers: ProviderHeaders | undefined): void {\n\tif (apiKey) return;\n\tif (\n\t\thasHeader(headers, \"authorization\") ||\n\t\thasHeader(headers, \"x-api-key\") ||\n\t\thasHeader(headers, \"cf-aig-authorization\")\n\t) {\n\t\treturn;\n\t}\n\tthrow new Error(`No API key for provider: ${provider}`);\n}\n\ninterface ServerSentEvent {\n\tevent: string | null;\n\tdata: string;\n\traw: string[];\n}\n\ninterface SseDecoderState {\n\tevent: string | null;\n\tdata: string[];\n\traw: string[];\n}\n\nconst ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([\n\t\"message_start\",\n\t\"message_delta\",\n\t\"message_stop\",","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/ai/src/api/anthropic-messages.ts#L288-L324","documentation":"Before building an Anthropic SDK client, the anthropic-messages stream function asserts authentication via assertRequestAuth: either options.apiKey is set, or the merged headers contain a non-empty authorization, x-api-key, or cf-aig-authorization value (case-insensitive keys). If none is present it throws 'No API key for provider: <provider>' (e.g. anthropic, github-copilot). Supplying options.client - such as an AnthropicVertex instance - skips the check entirely because auth lives inside that client.","triggerScenarios":"Calling stream or streamSimple on an anthropic-messages model with no apiKey option, no auth headers, and no custom client; the host resolves the key from an env var that is unset in the current shell, container, or CI runner; the key lookup returns empty string.","commonSituations":"ANTHROPIC_API_KEY (or the host's own env mapping) missing in CI, Docker, or a deployed environment; .env loaded after stream options are built; gateway setups (Cloudflare AI Gateway, Vertex) that should pass cf-aig-authorization headers or options.client instead of an API key.","solutions":["Set the apiKey option when building stream options, typically read from the provider's env var at startup","Or pass an auth header: authorization (Bearer ...), x-api-key, or cf-aig-authorization for Cloudflare AI Gateway","Or inject options.client (e.g. AnthropicVertex) when auth is not key-based","Fail fast at startup: assert the key resolves before the first request, not mid-conversation"],"exampleFix":"// before\nconst result = await streamSimple(model, context, {}); // no apiKey, no headers\n\n// after\nconst apiKey = process.env.ANTHROPIC_API_KEY;\nif (!apiKey) throw new Error(\"ANTHROPIC_API_KEY is not set\");\nconst result = await streamSimple(model, context, { apiKey });","handlingStrategy":"validation","validationCode":"const hasAuth = Boolean(\n  apiKey ||\n  headers?.authorization ||\n  headers?.[\"x-api-key\"] ||\n  headers?.[\"cf-aig-authorization\"],\n);\nif (!hasAuth && !options?.client) {\n  throw new Error(`No API key for provider: ${model.provider}`);\n}\nawait stream(model, context, { ...options, apiKey, headers });","typeGuard":"const canAuthenticate = (opts: { apiKey?: string; headers?: Record<string, string>; client?: unknown }): boolean =>\n  Boolean(opts.client || opts.apiKey || hasNonEmptyAuthHeader(opts.headers));","tryCatchPattern":null,"preventionTips":["Resolve and assert provider keys once at startup, failing fast with the env var name","Load .env files before building stream options","For gateway or Vertex setups, pass cf-aig-authorization headers or options.client instead of a key","Remember hasHeader requires a non-empty value - a blank string header does not count"],"tags":["api-key","authentication","anthropic","configuration","headers","missing-env-var"],"backgroundTag":"missing-api-key","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}