From 5566e9ec16aa4eacb7052a7450d52cd64d038db2 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Mon, 4 May 2026 00:05:06 +0900 Subject: [PATCH] =?UTF-8?q?fix(stability+terminal):=20stdout=20EPIPE=20?= =?UTF-8?q?=E2=86=92=20SIGABRT=20+=20zsh=20exec-flag-/=20rerun=20(v0.7.42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups to today's user-visible regressions. 1. local_bridge stdout EPIPE → SIGABRT -------------------------------------- User crash report (local_bridge-2026-05-03-230610.ips) traced to ``main.rs:71`` → ``std::io::stdio::_print`` → ``panic_fmt`` → ``rust_panic`` → ``abort``. v0.7.39 (b44f708) hardened the three ``eprintln!`` sites against EPIPE-induced ``panic = "abort"`` aborts but missed the matching ``println!`` sites at main.rs:52 (``--version`` banner) and main.rs:71 (one-shot JSON output). The latter is the path exercised every time the Python ctypes parent dies first and the bridge subprocess inherits a broken stdout pipe — reproducing the phantom ``DiagnosticReport`` the v0.7.39 commit was supposed to eliminate. Replaced both with ``let _ = writeln!(std::io::stdout(), ...)`` for parity with the stderr fix. 2. Open Remote Terminal: ``zsh:1: unknown exec flag -/`` again -------------------------------------------------------------- v0.7.31 (b2f9334) dropped the `` --- pyproject.toml | 2 +- rust/Cargo.lock | 12 ++++++------ rust/Cargo.toml | 2 +- rust/crates/local_bridge/src/main.rs | 18 ++++++++++++++++-- sublime/sessions/commands.py | 13 ++++++++++--- sublime/tests/test_cmd_connect.py | 7 +++++-- uv.lock | 2 +- 7 files changed, 40 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fc548a5..cd70406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sessions-sublime" -version = "0.7.41" +version = "0.7.42" description = "Sublime-facing Python code for Sessions." requires-python = ">=3.8" license = {text = "MIT"} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 097c1fa..0f0da79 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -221,7 +221,7 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "local_bridge" -version = "0.7.41" +version = "0.7.42" dependencies = [ "base64", "glob", @@ -432,7 +432,7 @@ dependencies = [ [[package]] name = "session_helper" -version = "0.7.41" +version = "0.7.42" dependencies = [ "base64", "notify", @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "session_protocol" -version = "0.7.41" +version = "0.7.42" dependencies = [ "base64", "serde", @@ -452,14 +452,14 @@ dependencies = [ [[package]] name = "sessions_askpass" -version = "0.7.41" +version = "0.7.42" dependencies = [ "tempfile", ] [[package]] name = "sessions_native" -version = "0.7.41" +version = "0.7.42" dependencies = [ "base64", "notify", @@ -773,7 +773,7 @@ dependencies = [ [[package]] name = "workspace_identity" -version = "0.7.41" +version = "0.7.42" [[package]] name = "zmij" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6d241d3..db6c834 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" [workspace.package] edition = "2024" license = "MIT" -version = "0.7.41" +version = "0.7.42" authors = ["Myeongseon Choi "] repository = "https://git.teahaven.kr/sublime-rs/sessions" homepage = "https://git.teahaven.kr/sublime-rs/sessions" diff --git a/rust/crates/local_bridge/src/main.rs b/rust/crates/local_bridge/src/main.rs index 4a79102..750e9a3 100644 --- a/rust/crates/local_bridge/src/main.rs +++ b/rust/crates/local_bridge/src/main.rs @@ -49,7 +49,12 @@ fn main() { .map(String::as_str) .is_some_and(|first| matches!(first, "--version" | "-V" | "version")) { - println!("{LOCAL_BRIDGE_VERSION_BANNER}"); + // Use ``writeln!`` + ``let _`` so EPIPE silently fails through to + // ``return`` instead of panicking → SIGABRT under + // ``panic = "abort"``. Same rationale as the eprintln sites + // hardened in v0.7.39 (b44f708): a parent that closes its end of + // our stdout before we write must not generate a phantom crash. + let _ = writeln!(std::io::stdout(), "{LOCAL_BRIDGE_VERSION_BANNER}"); return; } if args.first().map(String::as_str) == Some("lsp-stdio") { @@ -68,7 +73,16 @@ fn main() { match run(&args) { Ok(output) => match serde_json::to_string(&output) { Ok(encoded) => { - println!("{encoded}"); + // ``writeln!`` + ``let _`` here for the same reason as the + // eprintln sites hardened in v0.7.39: when Sublime / the + // Python ctypes parent dies first the bridge inherits a + // broken stdout pipe, and a bare ``println!`` panics on + // EPIPE → SIGABRT under ``panic = "abort"``. The earlier + // pass only covered stderr; this is the missed stdout + // site that produced the + // ``local_bridge::main hf88e153b048e40f5 main.rs:71`` + // abort signature in user crash reports. + let _ = writeln!(std::io::stdout(), "{encoded}"); } Err(error) => { let _ = writeln!( diff --git a/sublime/sessions/commands.py b/sublime/sessions/commands.py index 632956e..53b756c 100644 --- a/sublime/sessions/commands.py +++ b/sublime/sessions/commands.py @@ -6952,9 +6952,16 @@ class SessionsOpenRemoteTerminalCommand(sublime_plugin.WindowCommand): # out to break interactive zsh on some macOS → Linux setups # ("zsh: bad option: -/") — dropped in v0.7.30.5. ``ssh -t`` # already allocates a pty so the redirections were redundant. - remote_invocation = "cd {}; exec ${{SHELL:-/bin/sh}} -il".format( - shlex.quote(remote_root) - ) + # + # ``"$SHELL"`` not ``${SHELL:-/bin/sh}``: the ``:-`` fallback + # form re-tripped some zsh setups in v0.7.31+ with + # ``zsh:1: unknown exec flag -/`` — the parameter expansion + # split such that the literal ``-/bin/sh`` reached ``exec`` as + # a flag instead of expanding to ``/bin/sh``. sshd populates + # ``$SHELL`` from the user's passwd entry in every login + # session, so the fallback was redundant; quoting ``"$SHELL"`` + # also handles the rare path-with-spaces case. + remote_invocation = 'cd {}; exec "$SHELL" -il'.format(shlex.quote(remote_root)) # ``panel_name`` makes Terminus open the shell as a panel # docked at the bottom of the active window. Without it # Terminus defaults to a new tab in the editor pane group, diff --git a/sublime/tests/test_cmd_connect.py b/sublime/tests/test_cmd_connect.py index 4cbb91a..aafc9bc 100644 --- a/sublime/tests/test_cmd_connect.py +++ b/sublime/tests/test_cmd_connect.py @@ -450,12 +450,15 @@ def test_open_remote_terminal_opens_transient_terminus_pane( # handshake is racy. ``;`` not ``&&`` so a failed ``cd`` doesn't # take the shell down with it. The earlier ``