zeroclaw-labs/zeroclaw · error · anyhow::Error
amqp channel '{}': client_cert is set but client_key is miss
Error message
amqp channel '{}': client_cert is set but client_key is missing What it means
The AMQP channel was configured for TLS client authentication with client_cert set but client_key missing. lapin's OwnedIdentity needs a cert+key pair to build the PKCS#12 client identity, so build_client_identity refuses the half-pair during connect rather than attempting an anonymous connection against a broker expecting mTLS.
Source
Thrown at crates/zeroclaw-channels/src/amqp.rs:282
Connection::connect_with_config(
&self.amqp_url,
props,
OwnedTLSConfig {
identity,
cert_chain,
},
)
.await
.map_err(Into::into)
}
fn build_client_identity(&self) -> anyhow::Result<Option<OwnedIdentity>> {
let (cert_path, key_path) = match (&self.client_cert, &self.client_key) {
(Some(cert), Some(key)) => (cert, key),
(None, None) => return Ok(None),
(Some(_), None) => {
anyhow::bail!(
"amqp channel '{}': client_cert is set but client_key is missing",
self.alias
)
}
(None, Some(_)) => {
anyhow::bail!(
"amqp channel '{}': client_key is set but client_cert is missing",
self.alias
)
}
};
let cert_pem = std::fs::read(cert_path)?;
let key_pem = std::fs::read(key_path)?;
let der = pem_to_pkcs12_der(&cert_pem, &key_pem, &self.alias)?;
Ok(Some(OwnedIdentity {
der,View on GitHub (pinned to 88bb9c8533)
Solutions
- Set the matching client_key path in the same channel config block.
- Verify both files exist and are readable by the service user before restarting.
- If client-certificate auth is not required by the broker, remove client_cert as well — both unset means no client identity.
Example fix
# before [channels.amqp.prod] url = "amqps://broker:5671" client_cert = "/etc/zeroclaw/tls/client.pem" # after [channels.amqp.prod] url = "amqps://broker:5671" client_cert = "/etc/zeroclaw/tls/client.pem" client_key = "/etc/zeroclaw/tls/client.key"
Defensive patterns
Strategy: validation
Validate before calling
match (&cfg.client_cert, &cfg.client_key) {
(Some(_), None) | (None, Some(_)) => {
anyhow::bail!("client_cert and client_key must be set together");
}
_ => {}
} Type guard
fn has_complete_client_identity(cfg: &AmqpChannelConfig) -> bool {
cfg.client_cert.is_some() == cfg.client_key.is_some()
} Prevention
- Validate cert/key as a pair at config load, not at connect time
- Template both TLS paths from the same secret source
- Smoke-test mTLS with openssl s_client before deploying
When it happens
Trigger: AmqpChannelConfig (or the amqp channel config block) sets client_cert without client_key; connect() then calls build_client_identity and hits the (Some, None) arm.
Common situations: Secrets distributed as separate files where only the cert path got templated; config snippets copied between environments dropping one line; a key loader that failed silently and left the field unset.
Related errors
- amqp channel '{}': client_key is set but client_cert is miss
- amqp channel '{alias}': client_cert contains no certificates
- amqp.{}: dispatch = {:?} routes to the SOP engine but no SOP
- ACP request_permission failed: {} ({})
- ACP request_permission timed out after {timeout:?}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/1df82978d8c2da36.
Report an issue: GitHub.