From 4842a129f275ba162465db1a5a73c3f2d60b5c4d Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:31:33 -0400 Subject: [PATCH] Fix startup command and holder fallback --- src/bin/dosh-client.rs | 17 +++++++++-------- src/persist.rs | 6 +++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index 95950fa..d921b68 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -1048,7 +1048,7 @@ fn startup_command(args: &[String]) -> Option> { return None; } let mut command = args.join(" "); - command.push('\n'); + command.push('\r'); Some(command.into_bytes()) } @@ -1121,7 +1121,7 @@ fn expand_command_extension(template: &str, args: &[String]) -> String { fn startup_command_from_string(command: &str) -> Vec { let mut command = command.to_string(); - command.push('\n'); + command.push('\r'); command.into_bytes() } @@ -5540,6 +5540,7 @@ mod tests { assert!(Predictor::predictable(b"hello world")); assert!(!Predictor::predictable(b"")); assert!(!Predictor::predictable(b"\n")); + assert!(!Predictor::predictable(b"\r")); assert!(!Predictor::predictable(b"\x1b[A")); assert!(!Predictor::predictable("snowman: \u{2603}".as_bytes())); assert!(!Predictor::predictable("wide: 你".as_bytes())); @@ -5760,10 +5761,10 @@ mod tests { #[test] fn startup_command_joins_args_for_remote_shell() { assert_eq!(startup_command(&[]), None); - assert_eq!(startup_command(&["tm".to_string()]), Some(b"tm\n".to_vec())); + assert_eq!(startup_command(&["tm".to_string()]), Some(b"tm\r".to_vec())); assert_eq!( startup_command(&["tm".to_string(), "work".to_string()]), - Some(b"tm work\n".to_vec()) + Some(b"tm work\r".to_vec()) ); } @@ -5845,7 +5846,7 @@ mod tests { assert_eq!( resolved_startup_command(&["tm".into(), "work space".into()], &config, &host), - Some(b"tm 'work space'\n".to_vec()) + Some(b"tm 'work space'\r".to_vec()) ); } @@ -5872,7 +5873,7 @@ mod tests { assert_eq!( resolved_startup_command(&["tm".into(), "dosh".into()], &config, &host), - Some(b"/opt/tm/bin/tm --remote 'dosh'\n".to_vec()) + Some(b"/opt/tm/bin/tm --remote 'dosh'\r".to_vec()) ); } @@ -5913,11 +5914,11 @@ mod tests { assert_eq!( resolved_startup_command(&["echo".into(), "ok".into()], &config, &host), - Some(b"echo ok\n".to_vec()) + Some(b"echo ok\r".to_vec()) ); assert_eq!( resolved_startup_command(&[], &config, &host), - Some(b"tm\n".to_vec()) + Some(b"tm\r".to_vec()) ); host.default_command = None; diff --git a/src/persist.rs b/src/persist.rs index 171da1a..5af3c8f 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -28,6 +28,7 @@ use std::time::Duration; /// One-byte commands a server sends to a holder over its control socket after /// the holder has handed back the master fd. const HOLDER_CMD_SHUTDOWN: u8 = b'X'; +const HOLDER_STARTUP_TIMEOUT: Duration = Duration::from_millis(750); /// Magic written into a holder's `meta` file, bumped if the on-disk layout /// changes so a stale holder from an incompatible build is ignored. @@ -236,11 +237,14 @@ pub fn spawn_holder( // Wait (briefly) for the holder to come up. The holder forks/setsids before // creating the socket; once the socket exists we can connect. We also reap // the short-lived launcher child so it never becomes a zombie. - let deadline = std::time::Instant::now() + Duration::from_secs(5); + let deadline = std::time::Instant::now() + HOLDER_STARTUP_TIMEOUT; loop { if sock.exists() { break; } + if let Some(status) = child.try_wait().context("wait holder launcher")? { + bail!("holder launcher for session {session} exited before socket was ready: {status}"); + } if std::time::Instant::now() >= deadline { let _ = child.kill(); let _ = child.wait();