{"record":{"id":"e084268d21ff8b86","repo":"JuliusBrussee/caveman","slug":"aws-access-key-id-and-aws-secret-access-key-must-b","errorCode":null,"errorMessage":"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together","messagePattern":"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8424,"sourceCode":"  return kept.join(\"\\n\");\n}\n\nfunction bedrockCredentialEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {\n  const raw = env[name];\n  if (typeof raw !== \"string\" || !raw.trim()) return undefined;\n  if (/[\\r\\n]/.test(raw)) throw new Error(`${name} must not contain a newline`);\n  return raw.trim();\n}\n\nfunction bedrockUpstreamCredentialFromEnv(env: NodeJS.ProcessEnv): string | undefined {\n  const bearer = bedrockCredentialEnvValue(env, \"AWS_BEARER_TOKEN_BEDROCK\");\n  if (bearer) return bearer;\n  const accessKey = bedrockCredentialEnvValue(env, \"AWS_ACCESS_KEY_ID\");\n  const secretKey = bedrockCredentialEnvValue(env, \"AWS_SECRET_ACCESS_KEY\");\n  const sessionToken = bedrockCredentialEnvValue(env, \"AWS_SESSION_TOKEN\");\n  if (!accessKey && !secretKey && !sessionToken) return undefined;\n  if (!accessKey || !secretKey) {\n    throw new Error(\"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together\");\n  }\n  return [accessKey, secretKey, ...(sessionToken ? [sessionToken] : [])].join(\":\");\n}\n\n// applyClaudeBedrockWrap selects Claude Code's native Bedrock or opt-in Mantle\n// transport only when the operator explicitly requests it. The default Claude\n// profile remains Anthropic-wire. Runtime uses Claude Code's documented AWS\n// credential chain; unlike Mantle, Claude Code exposes no supported Runtime\n// authentication-bypass variable. In managed mode the validated BYOK value also\n// rides through Claude's custom headers. Stored-only, server-injected Claude\n// Code auth therefore uses Mantle's documented gateway mode.\nfunction applyClaudeBedrockWrap(env: NodeJS.ProcessEnv, agent: AgentProfile, renderedGw: string, modeGw: string): boolean {\n  if (agent.id !== \"claude\" || process.env.CAVEMAN_WRAP_PROVIDER?.trim().toLowerCase() !== \"bedrock\") return false;\n\n  const endpoint = process.env.CAVEMAN_BEDROCK_ENDPOINT?.trim().toLowerCase() || \"runtime\";\n  if (endpoint !== \"runtime\" && endpoint !== \"mantle\") {\n    throw new Error(\"CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle\");\n  }","sourceCodeStart":8406,"sourceCodeEnd":8442,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L8406-L8442","documentation":"In managed Bedrock wrap, bedrockUpstreamCredentialFromEnv builds the x-cave-upstream-key header from AWS credentials: AWS_BEARER_TOKEN_BEDROCK wins alone; otherwise SigV4-style keys are combined as 'accessKey:secretKey[:sessionToken]'. If any of the three key variables is set but the access key or secret key is missing (empty values count as unset), the pair is incomplete and credential construction is refused — a half-built credential would leak through as a broken header.","triggerScenarios":"`caveman wrap claude` with CAVEMAN_WRAP_PROVIDER=bedrock in managed mode, no AWS_BEARER_TOKEN_BEDROCK, and only one of AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY set (or only AWS_SESSION_TOKEN, e.g. from an assumed-role shell that exports just the session token).","commonSituations":"Reusing a shell where only the secret key leaked into env; CI jobs that inject session tokens without the key pair; aws sso logins exporting a subset of variables; copy-paste of one variable from a credentials file.","solutions":["Export both halves of the pair: `export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...` (plus AWS_SESSION_TOKEN only alongside both), then rerun the wrap","If you meant to use no static credentials, unset all three: `unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN`","If you have a bearer token, use `export AWS_BEARER_TOKEN_BEDROCK=<token>` instead — it takes priority and needs no pair","Audit where the lone variable came from (`env | grep AWS_`) so it does not reappear in later shells"],"exampleFix":"# before: only the secret key is set\nexport AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI\ncaveman claude\n# >> AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together\n\n# after: complete pair (or bearer token)\nexport AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nexport AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI\ncaveman claude","handlingStrategy":"validation","validationCode":"const has = (n) => Boolean(process.env[n] && process.env[n].trim());\nconst bearer = has('AWS_BEARER_TOKEN_BEDROCK');\nconst pair = has('AWS_ACCESS_KEY_ID') && has('AWS_SECRET_ACCESS_KEY');\nif (process.env.CAVEMAN_WRAP_PROVIDER === 'bedrock' && !bearer && !pair) {\n  throw new Error('set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY together (or AWS_BEARER_TOKEN_BEDROCK), or unset all three');\n}","typeGuard":"const isCompleteAwsKeyPair = (env) =>\n  Boolean((env.AWS_ACCESS_KEY_ID?.trim()) && (env.AWS_SECRET_ACCESS_KEY?.trim()));","tryCatchPattern":"catch (err) { if (err.message.includes('must be set together')) { /* set the missing half of the pair or unset both; session token alone is never valid */ } else throw err; }","preventionTips":["Export AWS key material as a complete pair (plus optional session token), never piecemeal","After `aws sso login` or role assumption, verify with `env | grep ^AWS_` that all three role vars landed","Prefer AWS_BEARER_TOKEN_BEDROCK when your provider issues single-line bearer tokens"],"tags":["aws","credentials","env-var","bedrock","claude"],"backgroundTag":"aws-credentials-missing","analyzedSha":"2f49f0e1a352aa810e70056b7930aeb0b3d219b4","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T10:36:37.832Z"}