{"record":{"id":"437817aa5530b524","repo":"JuliusBrussee/caveman","slug":"managed-bedrock-wrap-requires-a-valid-cave-api-key","errorCode":null,"errorMessage":"managed Bedrock wrap requires a valid CAVE_API_KEY","messagePattern":"managed Bedrock wrap requires a valid CAVE_API_KEY","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8201,"sourceCode":"  delete env.CLAUDE_CODE_USE_MANTLE;\n  delete env.CLAUDE_CODE_SKIP_MANTLE_AUTH;\n  delete env.ANTHROPIC_BEDROCK_MANTLE_BASE_URL;\n\n  const bedrockBase = appendUrlPath(renderedGw, \"/bedrock\");\n  if (endpoint === \"mantle\") {\n    env.CLAUDE_CODE_USE_MANTLE = \"1\";\n    env.CLAUDE_CODE_SKIP_MANTLE_AUTH = \"1\";\n    // Claude Code appends /v1/messages verbatim to this override. Caveman's\n    // explicit Mantle adapter route is /bedrock/anthropic/v1/messages.\n    env.ANTHROPIC_BEDROCK_MANTLE_BASE_URL = appendUrlPath(bedrockBase, \"/anthropic\");\n  } else {\n    env.CLAUDE_CODE_USE_BEDROCK = \"1\";\n    env.ANTHROPIC_BEDROCK_BASE_URL = bedrockBase;\n  }\n  if (wrapMode(modeGw) === \"managed\") {\n    const caveAPIKey = firstEnvSecret(env, [\"CAVE_API_KEY\"]);\n    if (!caveAPIKey || /[\\r\\n]/.test(caveAPIKey)) {\n      throw new Error(\"managed Bedrock wrap requires a valid CAVE_API_KEY\");\n    }\n    env.ANTHROPIC_CUSTOM_HEADERS = mergeAnthropicCustomHeader(\n      env.ANTHROPIC_CUSTOM_HEADERS,\n      \"x-cave-api-key\",\n      caveAPIKey,\n    );\n    const upstreamKey = bedrockUpstreamCredentialFromEnv(env);\n    env.ANTHROPIC_CUSTOM_HEADERS = mergeAnthropicCustomHeader(\n      env.ANTHROPIC_CUSTOM_HEADERS,\n      \"x-cave-upstream-key\",\n      upstreamKey,\n    );\n  }\n  return true;\n}\n\n// buildWrapEnv computes the child environment for a wrapped agent. It starts from\n// the generic provider base-URL union (the fail-open fallback — harmless for an","sourceCodeStart":8183,"sourceCodeEnd":8219,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/cli/src/index.ts#L8183-L8219","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","solutions":["Export a single-line key: `export CAVE_API_KEY=$(printf '%s' \"$CAVE_API_KEY\" | tr -d '\\r\\n')` using your real key, then rerun","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","In CI, reference the secret on one line (`CAVE_API_KEY: ${{ secrets.CAVE_API_KEY }}`), never via a '|' block scalar","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"],"exampleFix":"# before: managed bedrock wrap without a usable key\nexport CAVEMAN_WRAP_PROVIDER=bedrock\ncaveman claude\n# >> managed Bedrock wrap requires a valid CAVE_API_KEY\n\n# after: single-line key exported, wrap proceeds\nexport CAVE_API_KEY=cave_live_9f8e7d6c...\ncaveman claude","handlingStrategy":"validation","validationCode":"const key = process.env.CAVE_API_KEY;\nconst usable = typeof key === 'string' && key.length > 0 && !/[\\r\\n]/.test(key);\nif (process.env.CAVEMAN_WRAP_PROVIDER === 'bedrock' && isManagedGatewayUrl(process.env.CAVE_GATEWAY_URL) && !usable) {\n  throw new Error('export a single-line CAVE_API_KEY before managed bedrock wrap');\n}","typeGuard":"const isUsableApiKey = (v: unknown): v is string =>\n  typeof v === 'string' && v.length > 0 && !/[\\r\\n]/.test(v);","tryCatchPattern":"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; }","preventionTips":["Export CAVE_API_KEY with `tr -d '\\r\\n'` normalization in CI loaders","Local-mode wraps need no key — verify CAVE_GATEWAY_URL really points at the managed gateway before assuming key trouble","Keep the key in the environment of whatever shell/runner invokes caveman, not just your login shell"],"tags":["bedrock","managed-mode","api-key","env-var","claude","authentication"],"backgroundTag":"missing-env-var","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T08:17:14.275Z"}