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
- 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).
- 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.
- 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
- Add CAVE_API_KEY to your shell profile or secret manager so it is present in every environment the wrapper runs in.
- Never paste keys with trailing newlines into .env files; trim CR/LF when loading secrets programmatically.
- In CI, assert the key exists (and is newline-free) as a pre-flight step before invoking the wrapper.
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
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- bedrock action is not allowed
- bedrock base url invalid
- bedrock configured endpoint kind
- bedrock: could not build signing request
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 anView on GitHub (pinned to 3ee70a1026)