fix(maestro): make attach exit cleanly when session ends (NAP-226)
ci / rust (push) Successful in 2m15s
ci / rust (push) Successful in 2m15s
When a session terminates (the user runs `exit`, tmux is killed, or `maestro kill` is called) the daemon's per-session broadcast channel closes and `serve_attach` breaks out of its loop. The socket was then dropped without a Close frame, so the client's `stream.next()` would hang and the terminal stayed stuck in raw mode. - daemon: send `Message::Close(None)` before returning from `serve_attach` whenever the session itself ended (broadcast closed, or session was killed between auth and subscribe). - client: track remote-close, restore the TTY first, then print `[maestro] session ended` so the message lands in cooked mode. - tests: cover the kill path and the liveness-watcher reap path on the daemon side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,7 +134,7 @@ pub async fn run_attach(
|
||||
let (ws, _resp) = tokio_tungstenite::connect_async(&url).await?;
|
||||
let (mut sink, mut stream) = ws.split();
|
||||
|
||||
let _raw = RawTty::enable()?;
|
||||
let raw = RawTty::enable()?;
|
||||
|
||||
// Send initial window size so the remote PTY matches our terminal.
|
||||
if let Some((cols, rows)) = current_winsize() {
|
||||
@@ -156,12 +156,17 @@ pub async fn run_attach(
|
||||
"\r\n[maestro] detached (key 0x{detach_byte:02x})\r\n"
|
||||
));
|
||||
|
||||
let mut session_ended = false;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Server → local stdout
|
||||
msg = stream.next() => {
|
||||
match msg {
|
||||
None => break,
|
||||
None => {
|
||||
session_ended = true;
|
||||
break;
|
||||
}
|
||||
Some(Err(e)) => return Err(AttachError::WebSocket(e)),
|
||||
Some(Ok(Message::Binary(bytes))) => {
|
||||
stdout.write_all(&bytes).await?;
|
||||
@@ -177,7 +182,10 @@ pub async fn run_attach(
|
||||
Some(Ok(Message::Ping(p))) => {
|
||||
sink.send(Message::Pong(p)).await?;
|
||||
}
|
||||
Some(Ok(Message::Close(_))) => break,
|
||||
Some(Ok(Message::Close(_))) => {
|
||||
session_ended = true;
|
||||
break;
|
||||
}
|
||||
Some(Ok(_)) => {}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +220,13 @@ pub async fn run_attach(
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the TTY before printing so the message lands in cooked mode.
|
||||
drop(raw);
|
||||
if session_ended {
|
||||
let _ = stdout.write_all(b"[maestro] session ended\n").await;
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user