{"record":{"id":"c4b4a57b6827a660","repo":"zeroclaw-labs/zeroclaw","slug":"failed-to-build-http-client","errorCode":null,"errorMessage":"failed to build HTTP client","messagePattern":"failed to build HTTP client","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-channels/src/gmail_push.rs","lineNumber":172,"sourceCode":"    /// Resolves inbound external peers from canonical state at message-time.\n    /// No cache (see AGENTS.md \"ABSOLUTE RULE — SINGLE SOURCE OF TRUTH\").\n    pub peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync>,\n    http: Client,\n    last_history_id: Arc<Mutex<u64>>,\n    /// Sender half injected by the gateway to forward webhook-received messages.\n    pub tx: Arc<Mutex<Option<mpsc::Sender<ChannelMessage>>>>,\n}\n\nimpl GmailPushChannel {\n    pub fn new(\n        config: GmailPushConfig,\n        alias: impl Into<String>,\n        peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync>,\n    ) -> Self {\n        let http = Client::builder()\n            .timeout(Duration::from_secs(30))\n            .build()\n            .expect(\"failed to build HTTP client\");\n        Self {\n            config,\n            alias: alias.into(),\n            peer_resolver,\n            http,\n            last_history_id: Arc::new(Mutex::new(0)),\n            tx: Arc::new(Mutex::new(None)),\n        }\n    }\n\n    /// Register a Gmail watch subscription via `POST /gmail/v1/users/me/watch`.\n    pub async fn register_watch(&self) -> Result<WatchResponse> {\n        let token = self.config.oauth_token.clone();\n        if token.is_empty() {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/gmail_push.rs#L154-L190","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Reproduce outside the app with a tiny probe binary that just calls reqwest::Client::builder().build() to confirm it is environmental","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","As a stopgap, wrap channel construction in std::panic::catch_unwind at startup and fail with a diagnostic instead of an opaque panic"],"exampleFix":"// before — crates/zeroclaw-channels/src/gmail_push.rs\nlet http = Client::builder()\n    .timeout(Duration::from_secs(30))\n    .build()\n    .expect(\"failed to build HTTP client\");\n\n// after — propagate instead of panicking (change new() to return anyhow::Result<Self>)\nlet http = Client::builder()\n    .timeout(Duration::from_secs(30))\n    .build()\n    .map_err(|e| anyhow::anyhow!(\"failed to build Gmail HTTP client: {e}\"))?;","handlingStrategy":"validation","validationCode":"// probe the TLS/HTTP stack before wiring the channel\nfn http_stack_ok() -> bool {\n    reqwest::Client::builder().build().is_ok()\n}","typeGuard":null,"tryCatchPattern":"// last-resort containment until new() returns Result\nlet channel = std::panic::catch_unwind(|| {\n    GmailPushChannel::new(config, alias.clone(), peer_resolver)\n});\nmatch channel {\n    Ok(c) => c,\n    Err(_) => { /* log TLS backend failure; skip or abort channel startup with a diagnostic */ }\n}","preventionTips":["Ship ca-certificates plus the OpenSSL libs the binary links against (or build with rustls features) in every deployment image","Add a startup doctor check that smoke-tests reqwest::Client::builder().build() before wiring channels","Prefer rustls-based builds for portable/static binaries","File or carry an upstream change making GmailPushChannel::new return Result — expect() on a production path violates this repo's own AGENTS.md"],"tags":["rust","reqwest","tls","panic","gmail","channel","startup-crash"],"backgroundTag":"http-client-init-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}