From e5310570d9f3d3905dc898ad765f04d14c42ea3c Mon Sep 17 00:00:00 2001 From: Jason Witty Date: Mon, 17 Aug 2026 10:12:20 -0700 Subject: [PATCH] fix(socktop): explain how to install socktop_agent when demo mode can't find it Demo mode spawns a socktop_agent child process, but the agent is a separate crate that `cargo install socktop` does not pull in. When it was missing, the raw spawn error propagated to main and printed as `Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }`, which gives the user nothing to act on. Introduce DemoAgentError so a NotFound spawn failure is distinguishable from other io errors, and print the path we looked for plus the `cargo install socktop_agent` fix. Other spawn errors still propagate as before. Co-Authored-By: Claude Opus 5 (1M context) --- socktop/src/main.rs | 61 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/socktop/src/main.rs b/socktop/src/main.rs index 281a1c9..242901b 100644 --- a/socktop/src/main.rs +++ b/socktop/src/main.rs @@ -382,7 +382,16 @@ fn gather_intervals( async fn run_demo_mode(_tls_ca: Option<&str>) -> Result<(), Box> { let port = 3231; let url = format!("ws://127.0.0.1:{port}/ws"); - let child = spawn_demo_agent(port)?; + let child = match spawn_demo_agent(port) { + Ok(child) => child, + // The agent ships as its own binary, so a missing one is a setup problem, + // not a crash: tell the user how to fix it instead of dumping an io error. + Err(e @ DemoAgentError::NotFound(_)) => { + eprintln!("{e}"); + return Ok(()); + } + Err(e) => return Err(e.into()), + }; let mut app = App::new(); // Demo mode connects to localhost, so disable hostname verification tokio::select! { res=app.run(&url,None,false)=>{ drop(child); res } _=tokio::signal::ctrl_c()=>{ drop(child); Ok(()) } } @@ -399,9 +408,50 @@ impl Drop for DemoGuard { eprintln!("Stopped demo agent on port {}", self.port); } } -fn spawn_demo_agent(port: u16) -> Result> { +#[derive(Debug)] +enum DemoAgentError { + /// The socktop_agent executable could not be located. + NotFound(std::path::PathBuf), + Io(std::io::Error), +} + +impl std::fmt::Display for DemoAgentError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NotFound(candidate) => write!( + f, + "Could not start demo mode: '{}' was not found{}.\n\ + \n\ + Demo mode runs a local agent, which is shipped as a separate binary\n\ + and is not installed alongside the socktop TUI. Install it with:\n\ + \n cargo install socktop_agent\n\n\ + then run socktop again. See {} for other install options.", + candidate.display(), + // A bare file name means find_agent_executable() fell back to a PATH lookup. + if candidate.parent().is_none_or(|p| p.as_os_str().is_empty()) { + " on your PATH" + } else { + "" + }, + env!("CARGO_PKG_HOMEPAGE"), + ), + Self::Io(e) => write!(f, "Could not start demo mode: {e}"), + } + } +} + +impl std::error::Error for DemoAgentError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::NotFound(_) => None, + Self::Io(e) => Some(e), + } + } +} + +fn spawn_demo_agent(port: u16) -> Result { let candidate = find_agent_executable(); - let mut cmd = std::process::Command::new(candidate); + let mut cmd = std::process::Command::new(&candidate); cmd.arg("--port").arg(port.to_string()); cmd.env("SOCKTOP_ENABLE_SSL", "0"); @@ -409,7 +459,10 @@ fn spawn_demo_agent(port: u16) -> Result> //cmd.env("SOCKTOP_AGENT_GPU", "0"); //cmd.env("SOCKTOP_AGENT_TEMP", "0"); - let child = cmd.spawn()?; + let child = cmd.spawn().map_err(|e| match e.kind() { + std::io::ErrorKind::NotFound => DemoAgentError::NotFound(candidate), + _ => DemoAgentError::Io(e), + })?; std::thread::sleep(std::time::Duration::from_millis(300)); Ok(DemoGuard { port,