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
- Enable reqwest's rustls TLS backend in buzz-workflow so it matches the ring provider the relay installs
- 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
- 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
- Avoid expect() inside LazyLock/statics — a poisoned static turns one transient failure into a permanent one
- Initialize shared HTTP clients during component startup where errors can propagate
- Use the rustls backend consistently across the workspace to make client construction effectively infallible
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
- push HTTP client
- failed to install rustls crypto provider
- mesh ready-registry publish failed: {e}
- failed to install rustls crypto provider
- connect to test DB
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20).
Data as JSON: /api/errors/5594715111a4e79b.
Report an issue: GitHub.