{"record":{"id":"c3a598a422855279","repo":"justjavac/wechat-miniapp-radar","slug":"openai-api-key-is-not-configured","errorCode":null,"errorMessage":"OPENAI_API_KEY is not configured.","messagePattern":"OPENAI_API_KEY is not configured\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/ai-client.ts","lineNumber":130,"sourceCode":"    return JSON.parse(text) as ChatCompletionResponse;\n  } catch {\n    return null;\n  }\n}\n\nasync function requestChatCompletion<T>({\n  config,\n  model,\n  messages,\n  timeoutMs\n}: {\n  config: AiConfig;\n  model: string;\n  messages: AiPromptMessage[];\n  timeoutMs: number;\n}) {\n  const apiKey = process.env.OPENAI_API_KEY?.trim();\n  if (!apiKey) throw new Error(\"OPENAI_API_KEY is not configured.\");\n\n  const { response, text } = await fetchTextWithTimeout(\n    completionUrl(config),\n    {\n      method: \"POST\",\n      headers: {\n        \"content-type\": \"application/json\",\n        authorization: `Bearer ${apiKey}`,\n        ...openRouterHeaders(config)\n      },\n      body: JSON.stringify({\n        model,\n        messages,\n        temperature: 0.2,\n        max_tokens: DEFAULT_AI_MAX_TOKENS,\n        stream: false,\n        ...responseFormatForModel(config, model)\n      })","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/justjavac/wechat-miniapp-radar/blob/02a010ecea0320b7aa975bb62a5dde271ae630c9/lib/ai-client.ts#L112-L148","documentation":"Thrown by requestChatCompletion() in lib/ai-client.ts when process.env.OPENAI_API_KEY (trimmed) is empty at call time. It fires before any network call because the Authorization: Bearer header cannot be built without it. Important: the public createAiJsonCompletion() reads getAiConfig() and returns {ok:false,error:'AI is not configured.'} early when the key is absent, since in lib/ai-config.ts configured === apiKeyConfigured. Under a stable env this throw is therefore only reachable by calling the private requestChatCompletion() directly, or if the key is removed between the config snapshot and the request.","triggerScenarios":"Unit/integration tests calling requestChatCompletion() directly; a process where OPENAI_API_KEY was unset or cleared between getAiConfig() and the request; a harness that stubs getAiConfig() to report configured=true without exporting the key; OpenRouter users who set OPENAI_API_URL but forget the key.","commonSituations":".env missing OPENAI_API_KEY (note: an OpenRouter key also goes in OPENAI_API_KEY, not a separate var); key set in one Vercel environment but the function runs in another; a value of only whitespace that trims to empty; CI secret not injected for the job.","solutions":["Set OPENAI_API_KEY in .env locally and in Vercel project env (use your OpenRouter key when OPENAI_API_URL is https://openrouter.ai/api/v1).","Restart the dev server / redeploy after adding the var so the new value is loaded.","Guard with getAiConfig(): skip AI work when !config.apiKeyConfigured and use rule-based output.","Confirm via GET /api/health (reports AI integration status) or npm run integrations:verify."],"exampleFix":"// before\nconst value = await requestChatCompletion({ config, model, messages, timeoutMs });\n\n// after\nimport { getAiConfig } from \"@/lib/ai-config\";\nif (!getAiConfig().apiKeyConfigured) {\n  return ruleBasedFallback();\n}","handlingStrategy":"validation","validationCode":"import { getAiConfig } from \"@/lib/ai-config\";\nconst canCallAi = getAiConfig().apiKeyConfigured;\nif (!canCallAi) {\n  // do not call requestChatCompletion expecting a value\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat createAiJsonCompletion as the only entry point; it already short-circuits on a missing key.","For OpenRouter, put the OpenRouter key in OPENAI_API_KEY alongside OPENAI_API_URL=https://openrouter.ai/api/v1.","After rotating the key, redeploy or restart so the new value is loaded."],"tags":["ai","configuration","environment","authentication"],"backgroundTag":null,"analyzedSha":"02a010ecea0320b7aa975bb62a5dde271ae630c9","analyzedAt":"2026-08-12T16:16:33.227Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}