JuliusBrussee/caveman · error · Error

cannot safely resolve Qwen's effective OPENAI_API_KEY

Error message

cannot safely resolve Qwen's effective OPENAI_API_KEY

What it means

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.

Source

Thrown at packages/cli/src/index.ts:8841

  if (!getObject(overlay, ["mcp", "servers", "caveman"])) return overlay;
  const next = cloneJsonValue(overlay);
  const mcp = getObject(next, ["mcp"])!;
  const servers = getObject(next, ["mcp", "servers"])!;
  delete servers["caveman"];
  if (Object.keys(servers).length === 0) delete mcp["servers"];
  if (Object.keys(mcp).length === 0) delete next["mcp"];
  return next;
}

function applyConfigFileInjection(env: NodeJS.ProcessEnv, agent: AgentProfile, inj: ConfigFileInjection, gw: string, modeGw = gw, mcpMode: McpSurfaceMode = "auto", agentArgs: string[] = []) {
  const baseConfig = readBaseConfig(inj);
  const mode = wrapMode(modeGw);
  const staticOverlay = mode === "managed" && inj.config_overlay.managed !== undefined ? inj.config_overlay.managed : inj.config_overlay.local;
  const builder = overlayBuilders[agent.id];
  const renderOptions: RenderDeepOptions = {};
  if (agent.id === "qwen" && mode === "managed") {
    const available = qwenEffectiveOpenAIKeyAvailable();
    if (available === null) throw new Error("cannot safely resolve Qwen's effective OPENAI_API_KEY");
    renderOptions.optionalOpenAIKeyEnvAvailable = available;
  }
  let rawOverlay = renderDeep(
    builder ? builder(agent, baseConfig, { mode, gatewayUrl: gw, env }) : staticOverlay,
    gw,
    env,
    renderOptions,
  );
  if (agent.id === "qwen" && mcpMode === "auto") {
    const ownedMcp = ownedMcpRegistration(agent.id, agentArgs);
    if (ownedMcp) {
      // Qwen merges system settings last. Mirror only a still-journaled native
      // entry into this temporary system overlay so project settings cannot
      // replace the recovery server after Caveman has claimed it is available.
      rawOverlay = deepMerge(rawOverlay, { mcpServers: { caveman: qwenMcpEntry(ownedMcp) } });
    } else {
      // Qwen can advertise MCP schemas even when enterprise policy blocks calls.
      // Suppress the exact server in this highest-precedence temporary overlay

View on GitHub (pinned to 5184b3d11a)

Solutions

  1. 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.
  2. Set the key literally (a plain string, no ${env:...} references or newlines) in Qwen's settings.env or home env fallback.
  3. Repair or remove malformed Qwen settings layers (~/.qwen settings files, project dotfiles) that fail to parse.
  4. Unset QWEN_CODE_NO_RELAUNCH / SANDBOX if you don't specifically need them, so the normal relaunch resolution path can be used.
  5. Run `caveman setup`/doctor-style checks to confirm settings files are readable.

Example fix

// before: template reference in settings.env (unsafe to resolve)
OPENAI_API_KEY=${env:OPENAI_API_KEY}
// after: literal value in process env before wrap
export OPENAI_API_KEY="sk-..." && caveman qwen
Defensive patterns

Strategy: validation

Validate before calling

// Run before `caveman wrap qwen` in managed mode
const key = process.env.OPENAI_API_KEY;
if (!key || !key.trim()) throw new Error("Set OPENAI_API_KEY in the process env before wrapping qwen in managed mode");
if (/\$\{env:/.test(key) || /[\r\n]/.test(key)) throw new Error("OPENAI_API_KEY must be a literal string (no ${env:...} refs, no newlines)");

Type guard

function isLiteralApiKey(v: unknown): v is string {
  return typeof v === "string" && v.trim().length > 0 && !/\$\{env:/.test(v) && ![\r\n].test(v);
}

Try / catch

try {
  execSync("caveman qwen", { stdio: "inherit", env: { ...process.env, OPENAI_API_KEY: requiredKey } });
} catch (e) {
  if (e instanceof Error && e.message.includes("cannot safely resolve Qwen's effective OPENAI_API_KEY")) {
    console.error("Export a literal OPENAI_API_KEY and fix malformed ~/.qwen settings files, then retry.");
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

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

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

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06). Data as JSON: /api/errors/34621086bbc9999d. Report an issue: GitHub.