fix(maestro): recognise tmux 3.7's "server exited unexpectedly"

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) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-08-22 12:37:54 +01:00
co-authored by Claude Opus 5
parent ef3d314296
commit fc53d74850
+33 -3
View File
@@ -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();