JuliusBrussee/caveman · error · Error

CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle

Error message

CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle

What it means

When CAVEMAN_WRAP_PROVIDER=bedrock selects the Bedrock transport for Claude wrap, CAVEMAN_BEDROCK_ENDPOINT chooses the lane: 'runtime' (Claude Code native Bedrock, the default when unset/empty) or 'mantle' (Claude Code's Mantle transport pointed at the gateway's /bedrock/anthropic adapter). The value is trimmed and lowercased before comparison, so only genuinely other strings throw; anything else is a typo or a stale value from an older Caveman that accepted different names.

Source

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

  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 766dce6b13)

Solutions

  1. Use one of the two lane names: `export CAVEMAN_BEDROCK_ENDPOINT=runtime` or `export CAVEMAN_BEDROCK_ENDPOINT=mantle`
  2. If you wanted the default, just unset it: `unset CAVEMAN_BEDROCK_ENDPOINT` (empty/unset means runtime)
  3. Double-check you did not mean CAVEMAN_WRAP_PROVIDER (selects bedrock at all) or a gateway URL variable — URLs do not belong in CAVEMAN_BEDROCK_ENDPOINT
  4. Search your shell profile and CI env for the stray export and remove it

Example fix

# before: lane typo / URL in the lane variable
export CAVEMAN_WRAP_PROVIDER=bedrock
export CAVEMAN_BEDROCK_ENDPOINT=bedrock   # or http://127.0.0.1:3777/bedrock
caveman claude
# >> CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle

# after: valid lane (or unset for the default)
export CAVEMAN_BEDROCK_ENDPOINT=runtime
caveman claude
Defensive patterns

Strategy: validation

Validate before calling

const BEDROCK_ENDPOINTS = new Set(['runtime', 'mantle']);
const raw = process.env.CAVEMAN_BEDROCK_ENDPOINT;
const endpoint = raw?.trim().toLowerCase() || 'runtime';
if (process.env.CAVEMAN_WRAP_PROVIDER === 'bedrock' && !BEDROCK_ENDPOINTS.has(endpoint)) {
  throw new Error(`CAVEMAN_BEDROCK_ENDPOINT must be one of ${[...BEDROCK_ENDPOINTS].join('|')} (or unset)`);
}

Type guard

const isBedrockEndpointLane = (v: unknown): v is 'runtime' | 'mantle' =>
  v === undefined || v === '' || v === 'runtime' || v === 'mantle';

Try / catch

catch (err) { if (err.message.includes('must be runtime or mantle')) { /* unset the variable (default runtime) or set it to a lane name — URLs and synonyms are rejected */ } else throw err; }

Prevention

When it happens

Trigger: `caveman wrap claude` / `caveman claude` with CAVEMAN_WRAP_PROVIDER=bedrock and CAVEMAN_BEDROCK_ENDPOINT set to a non-empty string other than runtime/mantle — e.g. 'bedrock', 'gateway', 'native', 'local', or a URL like http://127.0.0.1:3777/bedrock.

Common situations: Copying an endpoint URL into the wrong variable; values left over from an older release or an internal build that spelled the lane differently; scripts parameterizing the endpoint name with a typo.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/dfeb968733e56d7b. Report an issue: GitHub.