can1357/oh-my-pi · error · ConfigurationError
Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must
Error message
Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.
What it means
This ConfigurationError is thrown when configuring mTLS client authentication for an Anthropic-compatible endpoint. mTLS requires BOTH a client certificate and its corresponding private key; providing only one is always a configuration mistake because the TLS handshake cannot complete. The library detects the partial configuration early and fails with a clear message instead of an opaque TLS error later.
Source
Thrown at packages/ai/src/providers/anthropic.ts:1321
}
}
return inline;
}
function resolveFoundryTlsOptions(model: Model<"anthropic-messages">): FoundryTlsOptions | undefined {
if (model.provider !== "anthropic") return undefined;
if (!isFoundryEnabled()) return undefined;
const cacheKey = foundryTlsOptionsCacheKey();
if (foundryTlsOptionsCache.has(cacheKey)) return foundryTlsOptionsCache.get(cacheKey);
const ca = resolvePemValue($env.NODE_EXTRA_CA_CERTS, "NODE_EXTRA_CA_CERTS");
const cert = resolvePemValue($env.CLAUDE_CODE_CLIENT_CERT, "CLAUDE_CODE_CLIENT_CERT");
const key = resolvePemValue($env.CLAUDE_CODE_CLIENT_KEY, "CLAUDE_CODE_CLIENT_KEY");
if ((cert && !key) || (!cert && key)) {
throw new AIError.ConfigurationError(
"Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.",
);
}
const options: FoundryTlsOptions = {};
if (ca) options.ca = [...tls.rootCertificates, ca];
if (cert) options.cert = cert;
if (key) options.key = key;
const resolved = Object.keys(options).length > 0 ? options : undefined;
foundryTlsOptionsCache.set(cacheKey, resolved);
return resolved;
}
function buildCoworkTlsFetchOptions(
model: Model<"anthropic-messages">,
baseUrl: string | undefined,
): AnthropicFetchOptions | undefined {
if (model.provider !== "anthropic") return undefined;View on GitHub (pinned to 9690622007)
Solutions
- Set both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY (PEM contents) in the environment before starting the process.
- If mTLS is not required, clear BOTH variables rather than just one.
- Check for typos in the variable names and verify resolvePemValue input (file path vs inline PEM) resolves to a non-empty value for both.
- Verify the key matches the certificate (same keypair); mismatched pairs fail even when both are set.
Example fix
// before export CLAUDE_CODE_CLIENT_CERT=$(cat client.pem) # CLAUDE_CODE_CLIENT_KEY not set // after export CLAUDE_CODE_CLIENT_CERT=$(cat client.pem) export CLAUDE_CODE_CLIENT_KEY=$(cat client-key.pem)
Defensive patterns
Strategy: validation
Validate before calling
const cert = process.env.CLAUDE_CODE_CLIENT_CERT;
const key = process.env.CLAUDE_CODE_CLIENT_KEY;
const mTLSRequested = Boolean(cert) !== Boolean(key);
if (mTLSRequested) {
throw new Error("Set BOTH CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY (or neither).");
} Type guard
function hasCompleteMtlsConfig(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv & { CLAUDE_CODE_CLIENT_CERT: string; CLAUDE_CODE_CLIENT_KEY: string } {
return (
(typeof env.CLAUDE_CODE_CLIENT_CERT === "string" && env.CLAUDE_CODE_CLIENT_CERT.length > 0) ===
(typeof env.CLAUDE_CODE_CLIENT_KEY === "string" && env.CLAUDE_CODE_CLIENT_KEY.length > 0)
);
} Prevention
- Store cert+key as a single paired secret (one secret containing both PEMs) so they rotate together.
- Add a startup assertion validating required env var pairs before constructing providers.
- Keep cert/key in one deployment template/manifest entry to avoid partial updates.
- Validate key matches cert (modulus/public-key comparison) in CI when rotating mTLS credentials.
When it happens
Trigger: Calling an Anthropic provider constructor/factory that builds a Foundry-style TLS config where exactly one of CLAUDE_CODE_CLIENT_CERT or CLAUDE_CODE_CLIENT_KEY is set in the environment (the other is unset or empty after resolvePemValue processing).
Common situations: Partial secrets setup in CI/CD (one secret added, the other forgotten); copying cert file content but forgetting the key (or vice versa) when proxying through a corporate mTLS gateway; rotating credentials and updating only one env var; typo in one of the two variable names.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- No model configured
- Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL
- Cannot register custom API "${api}": built-in API names are
- Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePa
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE must contain a JSON object
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ec30cf762bd905b4.
Report an issue: GitHub.