zeroclaw-labs/zeroclaw · critical

failed to build reqwest client

Error message

failed to build reqwest client

What it means

The SkillForge scout constructor builds a long-lived HTTP client with default headers and a 30-second timeout. `reqwest::Client::builder().build()` returns Err when the TLS backend cannot initialize (broken OpenSSL install, missing CA store, rustls/native-tls feature mismatch); `.expect()` turns that into a process abort at Scout::new time.

Source

Thrown at crates/zeroclaw-runtime/src/skillforge/scout.rs:92

        headers.insert(
            reqwest::header::ACCEPT,
            "application/vnd.github+json".parse().expect("valid header"),
        );
        headers.insert(
            reqwest::header::USER_AGENT,
            "ZeroClaw-SkillForge/0.1".parse().expect("valid header"),
        );
        if let Some(ref t) = token
            && let Ok(val) = format!("Bearer {t}").parse()
        {
            headers.insert(reqwest::header::AUTHORIZATION, val);
        }

        let client = reqwest::Client::builder()
            .default_headers(headers)
            .timeout(Duration::from_secs(30))
            .build()
            .expect("failed to build reqwest client");

        Self {
            client,
            queries: vec!["zeroclaw skill".into(), "ai agent skill".into()],
        }
    }

    /// Parse the GitHub search/repositories JSON response.
    fn parse_items(body: &serde_json::Value) -> Vec<ScoutResult> {
        let items = match body.get("items").and_then(|v| v.as_array()) {
            Some(arr) => arr,
            None => return vec![],
        };

        items
            .iter()
            .filter_map(|item| {
                let name = item.get("name")?.as_str()?.to_string();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Install or refresh certificates and the TLS library in the runtime image (e.g. `apt-get install -y ca-certificates`) or pin reqwest to the rustls-tls feature in Cargo.toml
  2. Construct the scout once at process startup so a TLS failure surfaces as a clean startup error rather than a mid-run panic
  3. Change `Scout::new` to return `Result<Self>` and propagate the build error with `?` instead of `.expect()`

Example fix

// before
let client = reqwest::Client::builder()
    .default_headers(headers)
    .timeout(Duration::from_secs(30))
    .build()
    .expect("failed to build reqwest client");

// after
let client = reqwest::Client::builder()
    .default_headers(headers)
    .timeout(Duration::from_secs(30))
    .build()?; // Scout::new returns anyhow::Result<Self>
Defensive patterns

Strategy: validation

Validate before calling

fn assert_http_stack_ready() -> anyhow::Result<()> {
    reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()
        .map_err(|e| anyhow::anyhow!("HTTP stack unusable: {e}"))?;
    Ok(())
}
// call once at process startup, before any scout is constructed

Prevention

When it happens

Trigger: Calling `Scout::new(...)` in a container without CA certificates, after a dependency upgrade changed the reqwest TLS backend, or on a host where the native TLS library cannot load.

Common situations: Minimal Docker images (alpine/distroless) missing ca-certificates; `cargo update` flipping between native-tls and rustls; air-gapped hosts with a broken OpenSSL; CI images without cert bundles.

Related errors


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