From f99647b2c2d7fbfd11269330a25ea4f9be59bafa Mon Sep 17 00:00:00 2001 From: Season Saw Date: Sat, 1 Aug 2026 21:17:23 +0800 Subject: fix: report a clear cli error when no herdr server is running (#1963) * fix: report a clear cli error when no herdr server is running socket cli commands surfaced a raw io::Error debug string (`Error: Os { code: 2, ... }`) when nothing was listening on the api socket, which read like a bad --cwd path. map dead-socket connect failures to a `server_not_running` json error carrying the resolved socket path, printed once at the edge that surfaces the error; recovering callers (plugin offline registry fallback, agent start polling) recognize the marker and keep their existing behavior. refs #1941 * fix: map dead-socket errors on the unchecked cli request path refs #1941 * fix: make server-not-running guidance session-aware refs #1941 --- tests/cli/sessions.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'tests') diff --git a/tests/cli/sessions.rs b/tests/cli/sessions.rs index 8cb0f4b7..ae7b8306 100644 --- a/tests/cli/sessions.rs +++ b/tests/cli/sessions.rs @@ -184,6 +184,69 @@ fn named_sessions_use_separate_servers_and_workspace_state() { cleanup_test_base(&base); } +#[test] +fn dead_server_cli_reports_one_session_aware_json_line() { + fn assert_server_not_running( + output: std::process::Output, + socket_path: &Path, + attach_command: &str, + ) { + assert_eq!( + output.status.code(), + Some(1), + "stdout={} stderr={}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!(output.stdout.is_empty(), "server errors belong on stderr"); + + let stderr = String::from_utf8(output.stderr).unwrap(); + let lines: Vec<_> = stderr.lines().collect(); + assert_eq!(lines.len(), 1, "expected exactly one JSON line: {stderr:?}"); + + let response: serde_json::Value = serde_json::from_str(lines[0]).unwrap(); + assert_eq!(response["id"], "cli:workspace:create"); + assert_eq!(response["error"]["code"], "server_not_running"); + assert_eq!( + response["error"]["message"], + format!( + "no herdr server is running at {}; run `{attach_command}` to start or attach it", + socket_path.display() + ) + ); + } + + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + fs::create_dir_all(&runtime_dir).unwrap(); + register_runtime_dir(&runtime_dir); + + let named_socket = named_session_socket(&config_home, "foo"); + let missing = run_named_cli( + &config_home, + &runtime_dir, + &["--session", "foo", "workspace", "create"], + ); + assert_server_not_running(missing, &named_socket, "herdr session attach foo"); + + let stale_socket = runtime_dir.join("stale.sock"); + drop(UnixListener::bind(&stale_socket).unwrap()); + let stale = Command::new(env!("CARGO_BIN_EXE_herdr")) + .args(["workspace", "create"]) + .env("XDG_CONFIG_HOME", &config_home) + .env("XDG_RUNTIME_DIR", &runtime_dir) + .env("HERDR_SOCKET_PATH", &stale_socket) + .env("HERDR_SESSION", "unrelated") + .env_remove("HERDR_CLIENT_SOCKET_PATH") + .env_remove("HERDR_ENV") + .output() + .unwrap(); + assert_server_not_running(stale, &stale_socket, "herdr"); + + cleanup_test_base(&base); +} + #[test] fn integration_commands_run_locally_when_server_is_missing() { let base = unique_test_dir(); -- cgit v1.2.3