zeroclaw-labs/zeroclaw · critical

failed to build HTTP client

Error message

failed to build HTTP client

What it means

GmailPushChannel::new builds a reqwest Client with a 30s timeout and calls .expect("failed to build HTTP client") (gmail_push.rs:169-172), so a builder failure panics inside the constructor instead of returning an error. reqwest's Client::builder().build() essentially only fails when the TLS backend cannot initialize (missing/broken native-tls/OpenSSL linkage, no system CA store, or a rustls feature misconfiguration); the timeout setting cannot cause it. Because new() returns Self rather than Result, the panic escapes to whoever wires the channel — typically crashing the gateway at startup. This also violates the repository's own AGENTS.md rule against expect() on production paths; upstream should propagate the error.

Source

Thrown at crates/zeroclaw-channels/src/gmail_push.rs:172

    /// Resolves inbound external peers from canonical state at message-time.
    /// No cache (see AGENTS.md "ABSOLUTE RULE — SINGLE SOURCE OF TRUTH").
    pub peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync>,
    http: Client,
    last_history_id: Arc<Mutex<u64>>,
    /// Sender half injected by the gateway to forward webhook-received messages.
    pub tx: Arc<Mutex<Option<mpsc::Sender<ChannelMessage>>>>,
}

impl GmailPushChannel {
    pub fn new(
        config: GmailPushConfig,
        alias: impl Into<String>,
        peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync>,
    ) -> Self {
        let http = Client::builder()
            .timeout(Duration::from_secs(30))
            .build()
            .expect("failed to build HTTP client");
        Self {
            config,
            alias: alias.into(),
            peer_resolver,
            http,
            last_history_id: Arc::new(Mutex::new(0)),
            tx: Arc::new(Mutex::new(None)),
        }
    }

    /// Register a Gmail watch subscription via `POST /gmail/v1/users/me/watch`.
    pub async fn register_watch(&self) -> Result<WatchResponse> {
        let token = self.config.oauth_token.clone();
        if token.is_empty() {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the TLS environment: install ca-certificates and the OpenSSL libraries the binary was linked against, or build zeroclaw-channels with rustls-based reqwest features
  2. Reproduce outside the app with a tiny probe binary that just calls reqwest::Client::builder().build() to confirm it is environmental
  3. Patch upstream: make new() return anyhow::Result<Self> (or inject a pre-built Client) so the failure is an error, not a panic — the repo's AGENTS.md forbids expect() on production paths
  4. As a stopgap, wrap channel construction in std::panic::catch_unwind at startup and fail with a diagnostic instead of an opaque panic

Example fix

// before — crates/zeroclaw-channels/src/gmail_push.rs
let http = Client::builder()
    .timeout(Duration::from_secs(30))
    .build()
    .expect("failed to build HTTP client");

// after — propagate instead of panicking (change new() to return anyhow::Result<Self>)
let http = Client::builder()
    .timeout(Duration::from_secs(30))
    .build()
    .map_err(|e| anyhow::anyhow!("failed to build Gmail HTTP client: {e}"))?;
Defensive patterns

Strategy: validation

Validate before calling

// probe the TLS/HTTP stack before wiring the channel
fn http_stack_ok() -> bool {
    reqwest::Client::builder().build().is_ok()
}

Try / catch

// last-resort containment until new() returns Result
let channel = std::panic::catch_unwind(|| {
    GmailPushChannel::new(config, alias.clone(), peer_resolver)
});
match channel {
    Ok(c) => c,
    Err(_) => { /* log TLS backend failure; skip or abort channel startup with a diagnostic */ }
}

Prevention

When it happens

Trigger: Constructing GmailPushChannel during gateway channel setup for a configured [channels.gmail.<alias>] on a host whose TLS dependencies fail to load: libssl absent or version-mismatched, no CA certificates, or a cross-compiled binary built without the matching TLS features.

Common situations: Slim or from-scratch Docker images lacking ca-certificates/libssl; musl cross-builds linked against native-tls; OS upgrade breaking OpenSSL 1.1 vs 3 linkage; Nix/static targets with wrong TLS features enabled.

Related errors


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