diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db63b17c..b07f5cd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `fleet spawn` and SDK placement no longer report `dispatchState: "dispatched"` for a spawn that was placed but never sent. Only `dispatched_node_id`, or a status the node moved, counts as dispatch evidence, so `fleet spawn` and `fleet spawn-status` agree. +- When a broker worker fails to spawn, the log now includes the underlying OS error, such as a missing binary, instead of only `failed to spawn worker`. ## [13.4.1] - 2026-10-12 diff --git a/crates/broker/src/runtime/relaycast_events.rs b/crates/broker/src/runtime/relaycast_events.rs index 0e3eaf288..65b18b784 100644 --- a/crates/broker/src/runtime/relaycast_events.rs +++ b/crates/broker/src/runtime/relaycast_events.rs @@ -1125,18 +1125,37 @@ pub(super) async fn spawn_worker_from_request( if msg.contains("already exists") { tracing::debug!(child = %name, "agent already spawned via SDK, skipping duplicate relaycast WS spawn"); } else { - tracing::error!(child = %name, error = %e, "failed to spawn worker via relaycast WS"); - eprintln!("[agent-relay] failed to spawn '{}': {}", name, e); + let detail = spawn_failure_detail(&e); + tracing::error!(child = %name, error = %detail, "failed to spawn worker via relaycast WS"); + eprintln!("[agent-relay] failed to spawn '{}': {}", name, detail); } Err(e) } } } +/// The whole error chain. Display alone stops at the outermost context +/// ("failed to spawn worker") and drops the OS error that says why. +fn spawn_failure_detail(error: &anyhow::Error) -> String { + format!("{error:#}") +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn spawn_failure_detail_keeps_the_os_error() { + let error = anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::NotFound)) + .context("failed to spawn worker"); + let detail = spawn_failure_detail(&error); + assert!(detail.starts_with("failed to spawn worker: "), "{detail}"); + assert!( + detail.contains(&std::io::Error::from(std::io::ErrorKind::NotFound).to_string()), + "{detail}" + ); + } + #[test] fn duplicate_registration_keeps_already_exists_for_debug_log_classification() { let error = ::relaycast::AgentRegistrationError::AlreadyExists {