From f9e81df5b13c622967fb43c7b9a95201f3ef94de Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Tue, 13 Oct 2026 10:34:26 -0700 Subject: [PATCH] fix(broker): log the whole error chain when a worker fails to spawn On fleet node sf-frame, `claude` spawns end with "failed to spawn worker" about 5ms after start. The log line formatted the anyhow error with Display, which prints only the outermost context, so the OS error from Command::spawn that says why the spawn failed was dropped. Format it with `{:#}` through a small helper, and pin the helper with a test that the io::Error survives. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 4 ++++ crates/broker/src/runtime/relaycast_events.rs | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ff77d81..0740e7507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `--sandbox-relayfile` to retain the previous required live repository mirror. Combine it with `--checkout` when both a Git checkout and live context must be ready before dispatch. +### Fixed + +- 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 ### Fixed 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 {