{"record":{"id":"8f11a91fb9998b50","repo":"zeroclaw-labs/zeroclaw","slug":"amqp-channel-client-key-is-set-but-client-ce","errorCode":null,"errorMessage":"amqp channel '{}': client_key is set but client_cert is missing","messagePattern":"amqp channel '(.+?)': client_key is set but client_cert is missing","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/amqp.rs","lineNumber":288,"sourceCode":"                cert_chain,\n            },\n        )\n        .await\n        .map_err(Into::into)\n    }\n\n    fn build_client_identity(&self) -> anyhow::Result<Option<OwnedIdentity>> {\n        let (cert_path, key_path) = match (&self.client_cert, &self.client_key) {\n            (Some(cert), Some(key)) => (cert, key),\n            (None, None) => return Ok(None),\n            (Some(_), None) => {\n                anyhow::bail!(\n                    \"amqp channel '{}': client_cert is set but client_key is missing\",\n                    self.alias\n                )\n            }\n            (None, Some(_)) => {\n                anyhow::bail!(\n                    \"amqp channel '{}': client_key is set but client_cert is missing\",\n                    self.alias\n                )\n            }\n        };\n\n        let cert_pem = std::fs::read(cert_path)?;\n        let key_pem = std::fs::read(key_path)?;\n        let der = pem_to_pkcs12_der(&cert_pem, &key_pem, &self.alias)?;\n\n        Ok(Some(OwnedIdentity {\n            der,\n            password: PKCS12_PASSWORD.to_string(),\n        }))\n    }\n\n    async fn establish_consumer(&self) -> anyhow::Result<(Connection, lapin::Consumer)> {\n        let conn = self.connect().await?;","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/amqp.rs#L270-L306","documentation":"The mirror of the missing-key case: client_key is set but client_cert is missing. lapin builds a PKCS#12 identity from a certificate chain plus private key, so build_client_identity rejects the incomplete pair during connect with an error naming the channel alias.","triggerScenarios":"AmqpChannelConfig sets client_key without client_cert — typically the cert path was dropped during config migration, templating, or environment promotion.","commonSituations":"Config generated from templates where the cert variable was empty; environments where the cert is managed differently from the key; manual edits adding the key first.","solutions":["Add the client_cert path pointing at the PEM certificate (leaf plus chain).","Confirm both TLS files are present and readable; then restart the channel.","If mTLS is not intended, remove client_key too so no client identity is attempted."],"exampleFix":"# before\n[channels.amqp.prod]\nurl = \"amqps://broker:5671\"\nclient_key = \"/etc/zeroclaw/tls/client.key\"\n\n# after\n[channels.amqp.prod]\nurl = \"amqps://broker:5671\"\nclient_cert = \"/etc/zeroclaw/tls/client.pem\"\nclient_key = \"/etc/zeroclaw/tls/client.key\"","handlingStrategy":"validation","validationCode":"match (&cfg.client_cert, &cfg.client_key) {\n    (Some(_), None) | (None, Some(_)) => {\n        anyhow::bail!(\"client_cert and client_key must be set together\");\n    }\n    _ => {}\n}","typeGuard":"fn has_complete_client_identity(cfg: &AmqpChannelConfig) -> bool {\n    cfg.client_cert.is_some() == cfg.client_key.is_some()\n}","tryCatchPattern":null,"preventionTips":["Treat the TLS identity as one unit in config schemas and templates","Fail config promotion when either half of the pair is missing","Lint channel configs for half-configured mTLS in CI"],"tags":["rust","amqp","mtls","tls","config-validation"],"backgroundTag":"missing-tls-client-cert","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}