JuliusBrussee/caveman · error
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
Error message
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together
What it means
When no Bedrock bearer token is present, the wrapper falls back to static AWS keys. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are only meaningful together; if exactly one of them (or only a session token) is set, the credential triple cannot be constructed, so it throws. AWS_SESSION_TOKEN alone is also insufficient.
Solutions
- Export both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY together (plus AWS_SESSION_TOKEN if using temporary credentials).
- Alternatively set AWS_BEARER_TOKEN_BEDROCK to skip the key-based path entirely.
- Audit your CI secret list / .env to confirm the key pair was not truncated or partially defined.
- If you have no Bedrock credentials, unset all three variables so the function cleanly returns undefined.
Example fix
// before (shell) export AWS_SESSION_TOKEN="FwoGZX..." // after export AWS_ACCESS_KEY_ID="AKIA..." export AWS_SECRET_ACCESS_KEY="wJalr..." export AWS_SESSION_TOKEN="FwoGZX..."
Defensive patterns
Strategy: validation
Validate before calling
const has = (k: string) => typeof process.env[k] === "string" && process.env[k]!.trim() !== "";
if (!has("AWS_BEARER_TOKEN_BEDROCK")) {
const a = has("AWS_ACCESS_KEY_ID"), s = has("AWS_SECRET_ACCESS_KEY");
if (a !== s) throw new Error("Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY together (or neither, using a bearer token)");
} Try / catch
try {
applyBedrockWrap(env, agent);
} catch (e) {
if (String((e as Error).message).includes("must be set together")) {
// surface which of the pair is missing and abort with instructions
}
} Prevention
- Export the AWS key pair (and session token) as a single atomic set in scripts/CI.
- Prefer AWS_BEARER_TOKEN_BEDROCK when using token-based Bedrock auth.
- Keep AWS credentials in one .env block; never comment out half a pair.
- Run an env preflight check before enabling CAVEMAN_WRAP_PROVIDER=bedrock.
When it happens
Trigger: CAVEMAN_WRAP_PROVIDER=bedrock with AWS_SESSION_TOKEN set but neither AWS_ACCESS_KEY_ID nor AWS_SECRET_ACCESS_KEY; or only AWS_ACCESS_KEY_ID exported (or only the secret key) in the shell/CI environment.
Common situations: Partially populated CI/CD secrets; exporting keys across two shell sessions; assuming a session token alone is usable; a .env file where one of the pair was commented out.
Related errors
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
- must not contain a newline
- awscreds: no AWS credentials found (env, web identity…
- bedrock: malformed AWS credentials (want…
- bedrock: missing AWS credentials in x-cave-upstream-key
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e084268d21ff8b86.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:9259
return kept.join("\n");
}
function bedrockCredentialEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
const raw = env[name];
if (typeof raw !== "string" || !raw.trim()) return undefined;
if (/[\r\n]/.test(raw)) throw new Error(`${name} must not contain a newline`);
return raw.trim();
}
function bedrockUpstreamCredentialFromEnv(env: NodeJS.ProcessEnv): string | undefined {
const bearer = bedrockCredentialEnvValue(env, "AWS_BEARER_TOKEN_BEDROCK");
if (bearer) return bearer;
const accessKey = bedrockCredentialEnvValue(env, "AWS_ACCESS_KEY_ID");
const secretKey = bedrockCredentialEnvValue(env, "AWS_SECRET_ACCESS_KEY");
const sessionToken = bedrockCredentialEnvValue(env, "AWS_SESSION_TOKEN");
if (!accessKey && !secretKey && !sessionToken) return undefined;
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");
}View on GitHub (pinned to 3ee70a1026)