{"record":{"id":"34621086bbc9999d","repo":"JuliusBrussee/caveman","slug":"cannot-safely-resolve-qwen-s-effective-openai-api","errorCode":null,"errorMessage":"cannot safely resolve Qwen's effective OPENAI_API_KEY","messagePattern":"cannot safely resolve Qwen's effective OPENAI_API_KEY","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8841,"sourceCode":"  if (!getObject(overlay, [\"mcp\", \"servers\", \"caveman\"])) return overlay;\n  const next = cloneJsonValue(overlay);\n  const mcp = getObject(next, [\"mcp\"])!;\n  const servers = getObject(next, [\"mcp\", \"servers\"])!;\n  delete servers[\"caveman\"];\n  if (Object.keys(servers).length === 0) delete mcp[\"servers\"];\n  if (Object.keys(mcp).length === 0) delete next[\"mcp\"];\n  return next;\n}\n\nfunction applyConfigFileInjection(env: NodeJS.ProcessEnv, agent: AgentProfile, inj: ConfigFileInjection, gw: string, modeGw = gw, mcpMode: McpSurfaceMode = \"auto\", agentArgs: string[] = []) {\n  const baseConfig = readBaseConfig(inj);\n  const mode = wrapMode(modeGw);\n  const staticOverlay = mode === \"managed\" && inj.config_overlay.managed !== undefined ? inj.config_overlay.managed : inj.config_overlay.local;\n  const builder = overlayBuilders[agent.id];\n  const renderOptions: RenderDeepOptions = {};\n  if (agent.id === \"qwen\" && mode === \"managed\") {\n    const available = qwenEffectiveOpenAIKeyAvailable();\n    if (available === null) throw new Error(\"cannot safely resolve Qwen's effective OPENAI_API_KEY\");\n    renderOptions.optionalOpenAIKeyEnvAvailable = available;\n  }\n  let rawOverlay = renderDeep(\n    builder ? builder(agent, baseConfig, { mode, gatewayUrl: gw, env }) : staticOverlay,\n    gw,\n    env,\n    renderOptions,\n  );\n  if (agent.id === \"qwen\" && mcpMode === \"auto\") {\n    const ownedMcp = ownedMcpRegistration(agent.id, agentArgs);\n    if (ownedMcp) {\n      // Qwen merges system settings last. Mirror only a still-journaled native\n      // entry into this temporary system overlay so project settings cannot\n      // replace the recovery server after Caveman has claimed it is available.\n      rawOverlay = deepMerge(rawOverlay, { mcpServers: { caveman: qwenMcpEntry(ownedMcp) } });\n    } else {\n      // Qwen can advertise MCP schemas even when enterprise policy blocks calls.\n      // Suppress the exact server in this highest-precedence temporary overlay","sourceCodeStart":8823,"sourceCodeEnd":8859,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L8823-L8859","documentation":"When wrapping Qwen in managed (routed) mode, the CLI must know whether a usable OPENAI_API_KEY would be in effect so it can render the optional key reference in the temp system settings. qwenEffectiveOpenAIKeyAvailable() returns null when it cannot PROVE the effective value: unreadable/ambiguous settings layers, unresolved excluded-env-vars or environment file paths, unparseable dotfiles, a settings.env that is not an object, non-string env values, or `${env:...}`-style reference/newline-bearing values in no-relaunch/sandbox mode. Rather than guess, applyConfigFileInjection throws this fail-closed error.","triggerScenarios":"Running `caveman wrap qwen` (or the qwen shortcut) against a managed gateway where: Qwen settings layers cannot be read; QWEN_CODE_NO_RELAUNCH or SANDBOX effective values cannot be safely resolved; OPENAI_API_KEY comes only from a workspace/system dotenv that fails to parse; or settings.env contains a non-string or `${env:...}` reference for OPENAI_API_KEY in no-relaunch/sandbox mode.","commonSituations":"Users keeping OPENAI_API_KEY only in a Qwen project `.env` file that the CLI cannot safely attribute; CI sandboxes where SANDBOX=1 and the key is referenced via a template instead of set literally; corrupted or hand-edited ~/.qwen settings files; unusual env-var quoting with embedded newlines.","solutions":["Set OPENAI_API_KEY explicitly in the process environment (export OPENAI_API_KEY=...) before running the wrap — process env is the first and safest tier.","Set the key literally (a plain string, no ${env:...} references or newlines) in Qwen's settings.env or home env fallback.","Repair or remove malformed Qwen settings layers (~/.qwen settings files, project dotfiles) that fail to parse.","Unset QWEN_CODE_NO_RELAUNCH / SANDBOX if you don't specifically need them, so the normal relaunch resolution path can be used.","Run `caveman setup`/doctor-style checks to confirm settings files are readable."],"exampleFix":"// before: template reference in settings.env (unsafe to resolve)\nOPENAI_API_KEY=${env:OPENAI_API_KEY}\n// after: literal value in process env before wrap\nexport OPENAI_API_KEY=\"sk-...\" && caveman qwen","handlingStrategy":"validation","validationCode":"// Run before `caveman wrap qwen` in managed mode\nconst key = process.env.OPENAI_API_KEY;\nif (!key || !key.trim()) throw new Error(\"Set OPENAI_API_KEY in the process env before wrapping qwen in managed mode\");\nif (/\\$\\{env:/.test(key) || /[\\r\\n]/.test(key)) throw new Error(\"OPENAI_API_KEY must be a literal string (no ${env:...} refs, no newlines)\");","typeGuard":"function isLiteralApiKey(v: unknown): v is string {\n  return typeof v === \"string\" && v.trim().length > 0 && !/\\$\\{env:/.test(v) && ![\\r\\n].test(v);\n}","tryCatchPattern":"try {\n  execSync(\"caveman qwen\", { stdio: \"inherit\", env: { ...process.env, OPENAI_API_KEY: requiredKey } });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"cannot safely resolve Qwen's effective OPENAI_API_KEY\")) {\n    console.error(\"Export a literal OPENAI_API_KEY and fix malformed ~/.qwen settings files, then retry.\");\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["Always export a literal OPENAI_API_KEY in the shell/CI environment for managed qwen wraps.","Never store the key as a ${env:...} template reference in Qwen settings.env.","Keep ~/.qwen settings files valid JSON and hand-edits minimal.","Avoid combining QWEN_CODE_NO_RELAUNCH or SANDBOX with key resolution that relies on late-loaded dotfiles."],"tags":["env-var","fail-closed","qwen","wrap","managed-mode"],"backgroundTag":"missing-env-var","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}