From fc53d74850c03be99388d5b448132371f2daf47d Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 12:37:54 +0100 Subject: [PATCH] fix(maestro): recognise tmux 3.7's "server exited unexpectedly" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Killing the last session shuts the server down, and tmux >= 3.7 reports that to the still-connected client with a phrasing `is_no_server_error` did not match. `list_sessions` then returned Err, and `run_liveness_pass` logs a warning and returns — so the pass reaped nothing. Silent by construction: nothing asserted the phrasing set, and the only symptom was an integration test that looked flaky. Both now covered. Co-Authored-By: Claude Opus 5 (1M context) --- crates/maestro-daemon/src/tmux.rs | 36 ++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/maestro-daemon/src/tmux.rs b/crates/maestro-daemon/src/tmux.rs index c7657c9..89abed6 100644 --- a/crates/maestro-daemon/src/tmux.rs +++ b/crates/maestro-daemon/src/tmux.rs @@ -263,12 +263,22 @@ impl Tmux { } } +/// Phrasings tmux uses for "there is no server on this socket". They vary by +/// version, and `list_sessions` turning one of these into an `Err` stops the +/// liveness watcher reaping for that pass — so a missing phrasing is silent. +const NO_SERVER_PHRASES: &[&str] = &[ + "no server running", + "no sessions", + "error connecting", + // tmux >= 3.7 when the server shuts down while this client is connected, + // which is exactly what killing the last session does. + "server exited unexpectedly", +]; + /// True if tmux's stderr indicates "there is no tmux server running on this /// socket" (multiple phrasings across versions). fn is_no_server_error(stderr: &str) -> bool { - stderr.contains("no server running") - || stderr.contains("no sessions") - || stderr.contains("error connecting") + NO_SERVER_PHRASES.iter().any(|p| stderr.contains(p)) } /// A session pane's process id, foreground command, controlling tty, and @@ -407,6 +417,26 @@ fn check(out: Output) -> Result<(), TmuxError> { mod tests { use super::*; + /// Every phrasing must be recognised: an unmatched one makes + /// `list_sessions` fail, and a failed pass reaps nothing. + #[test] + fn is_no_server_error_matches_every_known_phrasing() { + for phrase in NO_SERVER_PHRASES { + assert!(is_no_server_error(phrase), "unmatched: {phrase}"); + assert!( + is_no_server_error(&format!("{phrase}\n")), + "unmatched with trailing newline: {phrase}" + ); + } + } + + #[test] + fn is_no_server_error_rejects_unrelated_failures() { + assert!(!is_no_server_error("can't find session: maestro-x")); + assert!(!is_no_server_error("usage: kill-session")); + assert!(!is_no_server_error("")); + } + #[test] fn parse_pane_line_splits_pid_command_tty_path() { let p = parse_pane_line("12345 claude /dev/pts/3 /home/naps62/maestro").unwrap();