block/buzz · error

HTTP client build must succeed

Error message

HTTP client build must succeed

What it means

Workflow HTTP steps share one static LazyLock reqwest::Client with a 10s timeout. If Client::builder().build() fails (essentially only TLS-backend initialization), this expect panics — and because the client lives in a static LazyLock, the panic poisons it: every subsequent shared_http_client() call re-panics, permanently breaking all workflow HTTP steps in the process.

Source

Thrown at crates/buzz-workflow/src/executor.rs:974

    let body_text = String::from_utf8_lossy(&body_bytes).into_owned();

    Ok(serde_json::json!({
        "status": status,
        "body": body_text,
    }))
}

/// Returns a shared `reqwest::Client` reused across all workflow HTTP calls.
/// Sharing a single client reuses the underlying connection pool.
#[cfg(feature = "reqwest")]
fn shared_http_client() -> &'static reqwest::Client {
    use std::sync::LazyLock;
    use std::time::Duration;
    static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
        reqwest::Client::builder()
            .timeout(Duration::from_secs(10))
            .build()
            .expect("HTTP client build must succeed")
    });
    &CLIENT
}

/// POST `{"emoji": emoji}` to `POST /api/messages/{message_id}/reactions`.
#[cfg(feature = "reqwest")]
async fn add_reaction_impl(message_id: &str, emoji: &str) -> Result<JsonValue, WorkflowError> {
    let base_url =
        std::env::var("BUZZ_RELAY_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_owned());

    let url = format!("{base_url}/api/messages/{message_id}/reactions");

    let client = shared_http_client();

    let mut req = client
        .post(&url)
        .header("Content-Type", "application/json")
        .json(&serde_json::json!({ "emoji": emoji }));

View on GitHub (pinned to dad5a33865)

Solutions

  1. Enable reqwest's rustls TLS backend in buzz-workflow so it matches the ring provider the relay installs
  2. Build the client once during engine startup, store it in the executor state, and return Result so failures are reported once instead of poisoning a static
  3. Verify TLS prerequisites in the runtime image (CA bundle, OpenSSL versions) before shipping

Example fix

// before
static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
    reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()
        .expect("HTTP client build must succeed")
});

// after — construct once at startup, propagate the failure
let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(10))
    .build()
    .map_err(|e| WorkflowError::HttpInit(e.to_string()))?;
Defensive patterns

Strategy: validation

Validate before calling

// build once at executor startup and store in the engine
let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(10))
    .build()
    .map_err(|e| format!("workflow HTTP client init failed: {e}"))?;

Prevention

When it happens

Trigger: reqwest with the native-tls backend in an environment where OpenSSL cannot initialize; after the first failure any workflow invoking add_reaction or other HTTP tool calls panics its task even if the environment problem was transient.

Common situations: Minimal container images missing TLS runtime pieces; mixed TLS backends in the dependency tree; misconfigured SSL_CERT_FILE-style environment variables breaking OpenSSL init.

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20). Data as JSON: /api/errors/5594715111a4e79b. Report an issue: GitHub.