JuliusBrussee/caveman · error
CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle
Error message
CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle
What it means
applyClaudeBedrockWrap validates CAVEMAN_BEDROCK_ENDPOINT when CAVEMAN_WRAP_PROVIDER is "bedrock" for the claude agent. The variable accepts only "runtime" (default) or "mantle"; any other value is rejected because the wrapper can only remove the generic Anthropic route and wire exactly one of these two documented transport lanes.
Solutions
- Set CAVEMAN_BEDROCK_ENDPOINT=runtime (or remove it — "runtime" is the default).
- Set CAVEMAN_BEDROCK_ENDPOINT=mantle if you specifically want Claude Code's Mantle gateway transport.
- Do not put a real AWS endpoint URL in this variable; it is a mode selector, not a URL.
- Check casing after trimming/lowercasing is applied — "RUNTIME" is fine, "runtime-fips" is not.
Example fix
// before export CAVEMAN_BEDROCK_ENDPOINT="https://bedrock-runtime.us-east-1.amazonaws.com" // after export CAVEMAN_BEDROCK_ENDPOINT="runtime"
Defensive patterns
Strategy: validation
Validate before calling
const ep = (process.env.CAVEMAN_BEDROCK_ENDPOINT ?? "runtime").trim().toLowerCase();
if (process.env.CAVEMAN_WRAP_PROVIDER?.trim().toLowerCase() === "bedrock" && ep !== "runtime" && ep !== "mantle") {
throw new Error(`CAVEMAN_BEDROCK_ENDPOINT='${ep}' invalid; use "runtime" or "mantle"`);
} Type guard
function isBedrockEndpoint(v: string | undefined): v is "runtime" | "mantle" {
const s = (v ?? "runtime").trim().toLowerCase();
return s === "runtime" || s === "mantle";
} Try / catch
try {
applyClaudeBedrockWrap(env, agent, renderedGw, modeGw);
} catch (e) {
if (String((e as Error).message).startsWith("CAVEMAN_BEDROCK_ENDPOINT must be")) {
// fall back to default: delete the variable and retry with "runtime"
}
} Prevention
- Treat CAVEMAN_BEDROCK_ENDPOINT as a mode flag (runtime|mantle), never a URL.
- Remove the variable entirely to get the documented default (runtime).
- Add the allowed values to deployment docs/scripts that set CAVEMAN_WRAP_PROVIDER=bedrock.
- Normalize casing yourself (lowercase, trimmed) in wrapper scripts.
When it happens
Trigger: Setting CAVEMAN_WRAP_PROVIDER=bedrock and CAVEMAN_BEDROCK_ENDPOINT to something like a full https:// URL, "bedrock-runtime", "eu", or a typo such as "Runtime" beyond trimming/lowercasing is handled, so only genuinely different strings throw.
Common situations: Operators pasting a regional Bedrock endpoint URL into the variable assuming it wants an actual endpoint; typos like "mantle-gw"; copying the variable name from another tool's docs.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- must not contain a newline
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
- awscreds: no AWS credentials found (env, web identity…
- awscreds: read web identity token file
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/dfeb968733e56d7b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:9276
if (!accessKey || !secretKey) {
throw new Error("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together");
}
return [accessKey, secretKey, ...(sessionToken ? [sessionToken] : [])].join(":");
}
// applyClaudeBedrockWrap selects Claude Code's native Bedrock or opt-in Mantle
// transport only when the operator explicitly requests it. The default Claude
// profile remains Anthropic-wire. Runtime uses Claude Code's documented AWS
// credential chain; unlike Mantle, Claude Code exposes no supported Runtime
// authentication-bypass variable. In managed mode the validated BYOK value also
// rides through Claude's custom headers. Stored-only, server-injected Claude
// Code auth therefore uses Mantle's documented gateway mode.
function applyClaudeBedrockWrap(env: NodeJS.ProcessEnv, agent: AgentProfile, renderedGw: string, modeGw: string): boolean {
if (agent.id !== "claude" || process.env.CAVEMAN_WRAP_PROVIDER?.trim().toLowerCase() !== "bedrock") return false;
const endpoint = process.env.CAVEMAN_BEDROCK_ENDPOINT?.trim().toLowerCase() || "runtime";
if (endpoint !== "runtime" && endpoint !== "mantle") {
throw new Error("CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle");
}
// Remove the generic/profile Anthropic route and any stale Bedrock selection
// inherited from the shell. Exactly one Claude Code provider lane is active.
for (const key of WRAP_BASE_URL_ENV_VARS) delete env[key];
delete env.ANTHROPIC_AUTH_TOKEN;
delete env.CLAUDE_CODE_USE_BEDROCK;
// Remove stale values if a caller previously relied on this undocumented
// variable. We intentionally never set it.
delete env.CLAUDE_CODE_SKIP_BEDROCK_AUTH;
delete env.ANTHROPIC_BEDROCK_BASE_URL;
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";View on GitHub (pinned to 3ee70a1026)