zeroclaw-labs/zeroclaw · error

failed to build webhook HTTP client

Error message

failed to build webhook HTTP client

What it means

WebhookAudit::new() constructs a reqwest::Client with a 5-second timeout and expects success. Client::builder().build() fails essentially only when the TLS backend cannot initialize (missing/incompatible OpenSSL for native-tls, conflicting rustls/native-tls feature selection, or a corrupt CA store), so this panic means the webhook-audit hook could not get a usable HTTP client.

Source

Thrown at crates/zeroclaw-runtime/src/hooks/builtin/webhook_audit.rs:139

        if !config.url.is_empty()
            && let Err(e) = validate_webhook_url(&config.url)
        {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(
                        ::serde_json::json!({"hook": "webhook-audit", "error": format!("{}", e)})
                    ),
                "webhook URL validation failed"
            );
            panic!("webhook-audit: {e}");
        }

        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(5))
            .build()
            .expect("failed to build webhook HTTP client");
        Self {
            config,
            client,
            pending_args: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}

/// Simple glob matching: `*` matches any sequence of characters.
fn glob_matches(pattern: &str, text: &str) -> bool {
    if pattern == "*" {
        return true;
    }
    if !pattern.contains('*') {
        return pattern == text;
    }

    let parts: Vec<&str> = pattern.split('*').collect();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Install CA certificates in the image (apk add ca-certificates / apt-get install ca-certificates) if using native-tls.
  2. Unify the reqwest TLS feature across the workspace (prefer rustls-tls) so no runtime OpenSSL is required.
  3. Check dynamic linkage (ldd on the binary) for a missing or wrong-version libssl/libcrypto.
  4. If hook construction must not abort startup, gate webhook-audit behind a health check at deploy time.

Example fix

// before (Cargo.toml): mixed TLS features
reqwest = { version = "0.12", features = ["default-tls"] }

// after: one rustls-based stack, no runtime OpenSSL
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
Defensive patterns

Strategy: fallback

Validate before calling

// Deploy-time smoke test: fail the deploy, not the runtime panic.
fn tls_ok() -> bool {
    std::panic::catch_unwind(|| {
        reqwest::Client::builder().timeout(std::time::Duration::from_secs(5)).build().is_ok()
    }).unwrap_or(false)
}

Try / catch

// If native TLS init is broken, fall back to an explicit rustls client:
let client = match reqwest::Client::builder().timeout(Duration::from_secs(5)).build() {
    Ok(c) => c,
    Err(e) => {
        tracing::warn!("default TLS init failed ({e}); retrying with rustls");
        reqwest::Client::builder()
            .timeout(Duration::from_secs(5))
            .use_rustls_tls()
            .build()?
    }
};

Prevention

When it happens

Trigger: Instantiating the webhook-audit hook in an environment where the process's TLS stack fails to init: alpine/distroless images with native-tls but no ca-certificates, two crates forcing different reqwest TLS features, or an OS OpenSSL library with a broken version match.

Common situations: Deploying the agent into slim Docker images; adding a dependency that flips reqwest from rustls-tls to default-tls; glibc/OpenSSL mismatches after a base-image upgrade.

Related errors


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