JuliusBrussee/caveman · error · Error

managed Bedrock wrap requires a valid CAVE_API_KEY

Error message

managed Bedrock wrap requires a valid CAVE_API_KEY

What it means

In managed gateway mode (managed CAVE gateway URL), Bedrock-wrapped Claude must authenticate to the gateway itself, so Caveman requires CAVE_API_KEY: it must be present in the environment (firstEnvSecret), non-empty, and free of \r/\n — the key is delivered via the x-cave-api-key line of ANTHROPIC_CUSTOM_HEADERS, where a newline would enable header injection. Local-mode wraps do not hit this check because the local gateway does not authenticate.

Source

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

  delete env.CLAUDE_CODE_USE_MANTLE;
  delete env.CLAUDE_CODE_SKIP_MANTLE_AUTH;
  delete env.ANTHROPIC_BEDROCK_MANTLE_BASE_URL;

  const bedrockBase = appendUrlPath(renderedGw, "/bedrock");
  if (endpoint === "mantle") {
    env.CLAUDE_CODE_USE_MANTLE = "1";
    env.CLAUDE_CODE_SKIP_MANTLE_AUTH = "1";
    // Claude Code appends /v1/messages verbatim to this override. Caveman's
    // explicit Mantle adapter route is /bedrock/anthropic/v1/messages.
    env.ANTHROPIC_BEDROCK_MANTLE_BASE_URL = appendUrlPath(bedrockBase, "/anthropic");
  } else {
    env.CLAUDE_CODE_USE_BEDROCK = "1";
    env.ANTHROPIC_BEDROCK_BASE_URL = bedrockBase;
  }
  if (wrapMode(modeGw) === "managed") {
    const caveAPIKey = firstEnvSecret(env, ["CAVE_API_KEY"]);
    if (!caveAPIKey || /[\r\n]/.test(caveAPIKey)) {
      throw new Error("managed Bedrock wrap requires a valid CAVE_API_KEY");
    }
    env.ANTHROPIC_CUSTOM_HEADERS = mergeAnthropicCustomHeader(
      env.ANTHROPIC_CUSTOM_HEADERS,
      "x-cave-api-key",
      caveAPIKey,
    );
    const upstreamKey = bedrockUpstreamCredentialFromEnv(env);
    env.ANTHROPIC_CUSTOM_HEADERS = mergeAnthropicCustomHeader(
      env.ANTHROPIC_CUSTOM_HEADERS,
      "x-cave-upstream-key",
      upstreamKey,
    );
  }
  return true;
}

// buildWrapEnv computes the child environment for a wrapped agent. It starts from
// the generic provider base-URL union (the fail-open fallback — harmless for an

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Export a single-line key: `export CAVE_API_KEY=$(printf '%s' "$CAVE_API_KEY" | tr -d '\r\n')` using your real key, then rerun
  2. If wrapping locally, make sure the gateway URL actually resolves to local mode (CAVE_GATEWAY_URL / caveman start) — local mode needs no CAVE_API_KEY
  3. In CI, reference the secret on one line (`CAVE_API_KEY: ${{ secrets.CAVE_API_KEY }}`), never via a '|' block scalar
  4. Verify with `node -e 'console.log(process.env.CAVE_API_KEY?.length)'` that the variable is visible and non-empty in the exact shell that runs caveman

Example fix

# before: managed bedrock wrap without a usable key
export CAVEMAN_WRAP_PROVIDER=bedrock
caveman claude
# >> managed Bedrock wrap requires a valid CAVE_API_KEY

# after: single-line key exported, wrap proceeds
export CAVE_API_KEY=cave_live_9f8e7d6c...
caveman claude
Defensive patterns

Strategy: validation

Validate before calling

const key = process.env.CAVE_API_KEY;
const usable = typeof key === 'string' && key.length > 0 && !/[\r\n]/.test(key);
if (process.env.CAVEMAN_WRAP_PROVIDER === 'bedrock' && isManagedGatewayUrl(process.env.CAVE_GATEWAY_URL) && !usable) {
  throw new Error('export a single-line CAVE_API_KEY before managed bedrock wrap');
}

Type guard

const isUsableApiKey = (v: unknown): v is string =>
  typeof v === 'string' && v.length > 0 && !/[\r\n]/.test(v);

Try / catch

catch (err) { if (err.message.includes('requires a valid CAVE_API_KEY')) { /* export a clean single-line key (strip CR/LF), or switch the wrap to local mode; retry once */ } else throw err; }

Prevention

When it happens

Trigger: `caveman claude` / `caveman wrap claude` with CAVEMAN_WRAP_PROVIDER=bedrock and a managed gateway URL, when CAVE_API_KEY is unset, empty, whitespace-only, or contains a line break (e.g. copied with a trailing newline from a password manager or a CI block scalar).

Common situations: Forgetting to export the key in a fresh shell or CI job; the key living only in ~/.caveman-cloud credentials while the wrap expects it in env; a secrets manager appending \n; switching a working local-mode setup to the managed gateway without adding the key.

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@766dce6b13 (2026-08-18). Data as JSON: /api/errors/437817aa5530b524. Report an issue: GitHub.