{"record":{"id":"49ea0f1e46c5db49","repo":"tinyhumansai/openhuman","slug":"openai-api-key-is-required-unless-no-ai-or-dry","errorCode":null,"errorMessage":"OPENAI_API_KEY is required unless --no-ai or --dry-run is used","messagePattern":"OPENAI_API_KEY is required unless --no-ai or --dry-run is used","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/release/generate-release-notes.mjs","lineNumber":473,"sourceCode":"  if (typeof responseJson.output_text === 'string') {\n    return responseJson.output_text;\n  }\n\n  const parts = [];\n  for (const item of responseJson.output || []) {\n    for (const content of item.content || []) {\n      if (typeof content.text === 'string') {\n        parts.push(content.text);\n      }\n    }\n  }\n  return parts.join('\\n').trim();\n}\n\nasync function summarizeWithOpenAi(request) {\n  const key = getOpenAiKey();\n  if (!key) {\n    throw new Error('OPENAI_API_KEY is required unless --no-ai or --dry-run is used');\n  }\n\n  const timeoutMs = 300_000;\n  const controller = new AbortController();\n  const timeout = setTimeout(() => controller.abort(), timeoutMs);\n  let response;\n  try {\n    response = await fetch('https://api.openai.com/v1/responses', {\n      method: 'POST',\n      signal: controller.signal,\n      headers: {\n        Authorization: `Bearer ${key}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(request),\n    });\n  } catch (error) {\n    if (error?.name === 'AbortError') {","sourceCodeStart":455,"sourceCodeEnd":491,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/release/generate-release-notes.mjs#L455-L491","documentation":"summarizeWithOpenAi() needs an API key for the OpenAI Responses API. getOpenAiKey() checks OPENAI_API_KEY and the legacy OPENAI_API fallback and returned null, and neither --no-ai (deterministic Markdown rendering) nor --dry-run (print the request JSON instead of calling OpenAI) was passed — so the default AI path refuses to continue.","triggerScenarios":"Invoking generate-release-notes.mjs with no key in the environment and neither escape hatch flag; note the key can also come from OPENAI_API (legacy) — both being absent triggers it.","commonSituations":"Local shell where the env var was never exported; CI job whose secrets block does not map OPENAI_API_KEY to env; expecting the script to read a .env file (it does not — it reads process.env only).","solutions":["Export the key: export OPENAI_API_KEY=sk-... (or wire secrets.OPENAI_API_KEY into the CI job env)","Skip AI entirely: pass --no-ai to use renderDeterministicNotes()","Inspect the payload without a key: pass --dry-run to print the OpenAI request JSON"],"exampleFix":"# before\nnode scripts/release/generate-release-notes.mjs --from v1.2.3   # Error: OPENAI_API_KEY is required...\n\n# after — option A (provide key)\nOPENAI_API_KEY=\"$CI_OPENAI_KEY\" node scripts/release/generate-release-notes.mjs --from v1.2.3\n# after — option B (no AI dependency)\nnode scripts/release/generate-release-notes.mjs --from v1.2.3 --no-ai","handlingStrategy":"validation","validationCode":"if [ -z \"${OPENAI_API_KEY:-}\" ] && [ -z \"${OPENAI_API:-}\" ]; then\n  echo 'no OpenAI key — falling back to --no-ai' >&2\n  set -- \"$@\" --no-ai\nfi\nnode scripts/release/generate-release-notes.mjs \"$@\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["map secrets.OPENAI_API_KEY into the CI job env","make --no-ai the default in workflows that must not depend on OpenAI","remember the legacy OPENAI_API fallback exists when grepping for key wiring"],"tags":["openai","configuration","release-notes","environment"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}