{"record":{"id":"f7b7202de3777ccd","repo":"zeroclaw-labs/zeroclaw","slug":"client-key-is-set-but-client-cert-is-missing-both","errorCode":null,"errorMessage":"client_key is set but client_cert is missing (both are required for mutual TLS)","messagePattern":"client_key is set but client_cert is missing \\(both are required for mutual TLS\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":16624,"sourceCode":"        if !is_tls && !is_plain {\n            anyhow::bail!(\n                \"amqp_url must start with 'amqp://' or 'amqps://', got: {}\",\n                self.amqp_url\n            );\n        }\n\n        if is_tls && self.ca_cert.is_none() {\n            anyhow::bail!(\"amqps:// requires ca_cert to verify the broker\");\n        }\n\n        match (self.client_cert.is_some(), self.client_key.is_some()) {\n            (true, false) => {\n                anyhow::bail!(\n                    \"client_cert is set but client_key is missing (both are required for mutual TLS)\"\n                )\n            }\n            (false, true) => {\n                anyhow::bail!(\n                    \"client_key is set but client_cert is missing (both are required for mutual TLS)\"\n                )\n            }\n            _ => {}\n        }\n\n        if self.exchange.is_empty() {\n            validation_bail!(RequiredFieldEmpty, \"exchange\", \"exchange must not be empty\");\n        }\n\n        if self.routing_keys.is_empty() {\n            anyhow::bail!(\"at least one routing key must be configured\");\n        }\n\n        Ok(())\n    }\n}\n","sourceCodeStart":16606,"sourceCodeEnd":16642,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L16606-L16642","documentation":"The mirror of the cert-without-key check: a bare `client_key` with no `client_cert` is an incomplete mTLS identity and is rejected before connect. The validator's `(false, true)` match arm fires only for this shape, so the fix is always to supply the certificate half.","triggerScenarios":"`client_key = \"/etc/zeroclaw/client.key\"` with no `client_cert`; deleting or commenting the cert line while testing; reordered config where the cert key name was mistyped (e.g. `cert`) and thus ignored.","commonSituations":"Hand-editing mTLS blocks and dropping one line; secrets tooling that injects the key but fails to inject the cert path; key-past-first muscle memory when filling in PEM paths.","solutions":["Add the matching certificate: `client_cert = \"/etc/zeroclaw/client.pem\"`.","Check the exact field names `client_cert` / `client_key` — unknown keys are silently ignored, which produces exactly this error.","Verify the pair matches before reloading."],"exampleFix":"# before\nclient_key = \"/etc/zeroclaw/client.key\"\n# client_cert missing\n\n# after\nclient_key = \"/etc/zeroclaw/client.key\"\nclient_cert = \"/etc/zeroclaw/client.pem\"","handlingStrategy":"validation","validationCode":"anyhow::ensure!(\n    cfg.client_cert.is_some() == cfg.client_key.is_some(),\n    \"client_cert and client_key must be set together\"\n);","typeGuard":"fn mtls_pair_consistent(cert: &Option<PathBuf>, key: &Option<PathBuf>) -> bool {\n    cert.is_some() == key.is_some()\n}","tryCatchPattern":null,"preventionTips":["Check for mistyped key names — unknown TOML keys are ignored, which looks like a missing field.","Secrets-injection tooling should inject both paths or neither.","Keep the two paths adjacent in the config file so half-edits are obvious."],"tags":["amqp","mtls","config","certificates","zeroclaw"],"backgroundTag":"mtls-keypair-incomplete","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}