zeroclaw-labs/zeroclaw · error · anyhow::Error

amqp channel '{}': client_key is set but client_cert is miss

Error message

amqp channel '{}': client_key is set but client_cert is missing

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/amqp.rs:288

                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,
            password: PKCS12_PASSWORD.to_string(),
        }))
    }

    async fn establish_consumer(&self) -> anyhow::Result<(Connection, lapin::Consumer)> {
        let conn = self.connect().await?;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Add the client_cert path pointing at the PEM certificate (leaf plus chain).
  2. Confirm both TLS files are present and readable; then restart the channel.
  3. If mTLS is not intended, remove client_key too so no client identity is attempted.

Example fix

# before
[channels.amqp.prod]
url = "amqps://broker:5671"
client_key = "/etc/zeroclaw/tls/client.key"

# 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

When it happens

Trigger: AmqpChannelConfig sets client_key without client_cert — typically the cert path was dropped during config migration, templating, or environment promotion.

Common situations: 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.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8f11a91fb9998b50. Report an issue: GitHub.