{"record":{"id":"ec30cf762bd905b4","repo":"can1357/oh-my-pi","slug":"both-claude-code-client-cert-and-claude-code-clien","errorCode":null,"errorMessage":"Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.","messagePattern":"Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS\\.","errorType":"exception","errorClass":"ConfigurationError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic.ts","lineNumber":1321,"sourceCode":"\t\t}\n\t}\n\n\treturn inline;\n}\n\nfunction resolveFoundryTlsOptions(model: Model<\"anthropic-messages\">): FoundryTlsOptions | undefined {\n\tif (model.provider !== \"anthropic\") return undefined;\n\tif (!isFoundryEnabled()) return undefined;\n\n\tconst cacheKey = foundryTlsOptionsCacheKey();\n\tif (foundryTlsOptionsCache.has(cacheKey)) return foundryTlsOptionsCache.get(cacheKey);\n\n\tconst ca = resolvePemValue($env.NODE_EXTRA_CA_CERTS, \"NODE_EXTRA_CA_CERTS\");\n\tconst cert = resolvePemValue($env.CLAUDE_CODE_CLIENT_CERT, \"CLAUDE_CODE_CLIENT_CERT\");\n\tconst key = resolvePemValue($env.CLAUDE_CODE_CLIENT_KEY, \"CLAUDE_CODE_CLIENT_KEY\");\n\n\tif ((cert && !key) || (!cert && key)) {\n\t\tthrow new AIError.ConfigurationError(\n\t\t\t\"Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.\",\n\t\t);\n\t}\n\n\tconst options: FoundryTlsOptions = {};\n\tif (ca) options.ca = [...tls.rootCertificates, ca];\n\tif (cert) options.cert = cert;\n\tif (key) options.key = key;\n\tconst resolved = Object.keys(options).length > 0 ? options : undefined;\n\tfoundryTlsOptionsCache.set(cacheKey, resolved);\n\treturn resolved;\n}\n\nfunction buildCoworkTlsFetchOptions(\n\tmodel: Model<\"anthropic-messages\">,\n\tbaseUrl: string | undefined,\n): AnthropicFetchOptions | undefined {\n\tif (model.provider !== \"anthropic\") return undefined;","sourceCodeStart":1303,"sourceCodeEnd":1339,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic.ts#L1303-L1339","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nexport CLAUDE_CODE_CLIENT_CERT=$(cat client.pem)\n# CLAUDE_CODE_CLIENT_KEY not set\n\n// after\nexport CLAUDE_CODE_CLIENT_CERT=$(cat client.pem)\nexport CLAUDE_CODE_CLIENT_KEY=$(cat client-key.pem)","handlingStrategy":"validation","validationCode":"const cert = process.env.CLAUDE_CODE_CLIENT_CERT;\nconst key = process.env.CLAUDE_CODE_CLIENT_KEY;\nconst mTLSRequested = Boolean(cert) !== Boolean(key);\nif (mTLSRequested) {\n  throw new Error(\"Set BOTH CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY (or neither).\");\n}","typeGuard":"function hasCompleteMtlsConfig(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv & { CLAUDE_CODE_CLIENT_CERT: string; CLAUDE_CODE_CLIENT_KEY: string } {\n  return (\n    (typeof env.CLAUDE_CODE_CLIENT_CERT === \"string\" && env.CLAUDE_CODE_CLIENT_CERT.length > 0) ===\n    (typeof env.CLAUDE_CODE_CLIENT_KEY === \"string\" && env.CLAUDE_CODE_CLIENT_KEY.length > 0)\n  );\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["configuration","mtls","environment-variables"],"backgroundTag":"missing-env-var","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}