JuliusBrussee/caveman · error

managed Bedrock wrap requires a valid CAVE_API_KEY

Error message

managed Bedrock wrap requires a valid CAVE_API_KEY

What it means

When Caveman wraps the Claude agent for managed Bedrock routing, it must forward your identity to the Cave gateway via an `x-cave-api-key` header. Before injecting that header it reads CAVE_API_KEY from the process environment; if the variable is unset/empty, or if it contains a CR or LF character (which would allow HTTP header injection into ANTHROPIC_CUSTOM_HEADERS), the wrapper aborts the launch rather than produce a broken or unsafe request. This is a deliberate fail-fast: routing through managed Bedrock without a valid key cannot work.

Solutions

  1. Export a valid CAVE_API_KEY in the environment before launching: export CAVE_API_KEY=<key from your Caveman dashboard> (or add it to the env your wrapper actually reads).
  2. Strip whitespace/newlines from the value: export CAVE_API_KEY=$(printf '%s' "$CAVE_API_KEY" | tr -d '\r\n'), or fix the .env file so the key is on one line without escaped \n.
  3. If you do not intend managed Bedrock routing, switch the wrap mode/gateway configuration so wrapMode(gw) !== "managed" (e.g. use direct Bedrock credentials instead of the managed wrap).

Example fix

// before (missing or malformed secret)
CLAUDE_CODE_USE_BEDROCK=1 npx caveman agent claude   # throws: managed Bedrock wrap requires a valid CAVE_API_KEY
// after
export CAVE_API_KEY="sk-cave-..."   # single line, no CR/LF
CLAUDE_CODE_USE_BEDROCK=1 npx caveman agent claude
Defensive patterns

Strategy: validation

Validate before calling

const key = process.env.CAVE_API_KEY;
if (!key || /[\r\n]/.test(key)) {
  throw new Error("Refusing to launch managed Bedrock wrap: CAVE_API_KEY missing or contains newline");
}

Try / catch

try {
  launchManagedBedrockWrap();
} catch (e) {
  if (e instanceof Error && e.message.includes("CAVE_API_KEY")) {
    console.error("Set CAVE_API_KEY (single line, no CR/LF) before using managed Bedrock wrap.");
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the CLI launcher (the code path in packages/cli/src/index.ts around buildWrapEnv/applyClaudeBedrockWrap) with a Bedrock wrap mode (env.CLAUDE_CODE_USE_BEDROCK="1" and ANTHROPIC_BEDROCK_BASE_URL set to the managed base URL) while (a) CAVE_API_KEY is not exported in the environment passed to firstEnvSecret, or (b) CAVE_API_KEY contains a carriage return or newline character.

Common situations: Running the managed Bedrock wrapper in CI or a fresh shell where CAVE_API_KEY was never exported; a .env loader that preserves the literal '\n' escape in the key value; copy-pasting the key from a document with a trailing newline; wrapping multi-line secrets from a secret manager into the env var.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/437817aa5530b524. Report an issue: GitHub.

Appendix: source

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

  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 3ee70a1026)