diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b3835dee..a2200473 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,7 +15,7 @@ anyhow = { workspace = true } tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -reqwest = { version = "0.12", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } colored = "2.1" chrono = { workspace = true } uuid = { version = "1.11", features = ["serde", "v4"] } diff --git a/crates/cli/src/client.rs b/crates/cli/src/client.rs index fef74a5b..78c49ce8 100644 --- a/crates/cli/src/client.rs +++ b/crates/cli/src/client.rs @@ -95,7 +95,14 @@ impl ApiClient { /// /// * `base_url` - The base URL for all requests (e.g., "http://localhost:7004") pub fn new(base_url: String) -> Self { - Self { client: reqwest::Client::new(), base_url } + // Use no_proxy() to skip macOS system proxy detection via + // hyper-util → system-configuration, which panics in sandboxed + // environments where SCDynamicStoreCreate() returns NULL. + // The CLI only ever talks to localhost services, so proxy + // settings are irrelevant. + let client = + reqwest::Client::builder().no_proxy().build().expect("Failed to build HTTP client"); + Self { client, base_url } } /// Make a GET request and deserialize the JSON response. @@ -329,40 +336,24 @@ impl ApiClient { mod tests { use super::*; - /// Construct an `ApiClient`, gracefully handling the macOS - /// `system-configuration` TLS initialisation panic that occurs when - /// `reqwest::Client::new()` is called from a non-main test thread. - /// - /// The side-effect of the (possibly panicking) TLS initialisation is - /// still observed by subsequent tests in the same process, allowing - /// mockito-based tests to bind sockets correctly. - fn try_new_client(url: &str) -> Option { - std::panic::catch_unwind(|| ApiClient::new(url.to_string())).ok() - } - #[test] fn test_client_creation() { - match try_new_client("http://localhost:7004") { - Some(client) => assert_eq!(client.base_url, "http://localhost:7004"), - None => {} // macOS TLS init panic — acceptable in test threads - } + let client = ApiClient::new("http://localhost:7004".to_string()); + assert_eq!(client.base_url, "http://localhost:7004"); } #[test] fn test_client_clone() { - if let Some(client1) = try_new_client("http://localhost:7004") { - let client2 = client1.clone(); - assert_eq!(client1.base_url, client2.base_url); - } + let client1 = ApiClient::new("http://localhost:7004".to_string()); + let client2 = client1.clone(); + assert_eq!(client1.base_url, client2.base_url); } #[test] fn test_client_with_different_base_urls() { - if let (Some(c1), Some(c2)) = - (try_new_client("http://localhost:7004"), try_new_client("http://localhost:7001")) - { - assert_eq!(c1.base_url, "http://localhost:7004"); - assert_eq!(c2.base_url, "http://localhost:7001"); - } + let c1 = ApiClient::new("http://localhost:7004".to_string()); + let c2 = ApiClient::new("http://localhost:7001".to_string()); + assert_eq!(c1.base_url, "http://localhost:7004"); + assert_eq!(c2.base_url, "http://localhost:7001"); } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e6a49b8a..740cf504 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -423,7 +423,10 @@ struct ServiceStatus { } async fn check_all_services(json: bool) -> Result<()> { - let http = reqwest::Client::builder().timeout(std::time::Duration::from_secs(3)).build()?; + let http = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(3)) + .no_proxy() // avoid SCDynamicStoreCreate panic in sandboxed environments + .build()?; let checks: Vec<(&str, String)> = SERVICES .iter() diff --git a/crates/wrap/Cargo.toml b/crates/wrap/Cargo.toml index 5644d7a8..5cf0e1f8 100644 --- a/crates/wrap/Cargo.toml +++ b/crates/wrap/Cargo.toml @@ -22,7 +22,7 @@ axum = { workspace = true } tower-http = { workspace = true } chrono = { workspace = true } uuid = { version = "1.11", features = ["v4", "serde"] } -reqwest = { version = "0.12", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } agentd-common = { path = "../common" } async-trait = "0.1" metrics = { workspace = true } diff --git a/crates/wrap/src/client.rs b/crates/wrap/src/client.rs index 311533ef..d3092907 100644 --- a/crates/wrap/src/client.rs +++ b/crates/wrap/src/client.rs @@ -83,7 +83,14 @@ impl WrapClient { /// let client = WrapClient::new("http://localhost:7005"); /// ``` pub fn new(base_url: impl Into) -> Self { - Self { client: reqwest::Client::new(), base_url: base_url.into() } + // Use no_proxy() to skip macOS system proxy detection via + // hyper-util → system-configuration, which panics in sandboxed + // environments where SCDynamicStoreCreate() returns NULL. + // The wrap service only talks to local services; proxy settings + // are irrelevant here. + let client = + reqwest::Client::builder().no_proxy().build().expect("Failed to build HTTP client"); + Self { client, base_url: base_url.into() } } /// Launch an agent in a tmux session.