fix(stability+terminal): stdout EPIPE → SIGABRT + zsh exec-flag-/ rerun (v0.7.42)
All checks were successful
boundary-lint / PR boundary-claim (Lint (push) Has been skipped
boundary-lint / duplication-deadline (Layer 1/2) (push) Successful in 18s
ci / test-health gate (push) Successful in 16s
ci / mutation test (broker) (push) Has been skipped
boundary-lint / ban-list lint (Lint (push) Successful in 19s
Release Publish (Gitea session_helper) / verify-release-tag (push) Successful in 17s
ci / rust debug (push) Successful in 2m17s
ci / rust release (push) Successful in 2m24s
ci / python (push) Successful in 1m37s
Release Publish (Gitea session_helper) / publish-linux-x86_64 (push) Successful in 3m42s
All checks were successful
boundary-lint / PR boundary-claim (Lint (push) Has been skipped
boundary-lint / duplication-deadline (Layer 1/2) (push) Successful in 18s
ci / test-health gate (push) Successful in 16s
ci / mutation test (broker) (push) Has been skipped
boundary-lint / ban-list lint (Lint (push) Successful in 19s
Release Publish (Gitea session_helper) / verify-release-tag (push) Successful in 17s
ci / rust debug (push) Successful in 2m17s
ci / rust release (push) Successful in 2m24s
ci / python (push) Successful in 1m37s
Release Publish (Gitea session_helper) / publish-linux-x86_64 (push) Successful in 3m42s
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 ``</dev/tty`` redirect prefix that broke ``zsh: bad option: -/``. The remaining ``${SHELL:-/bin/sh}`` default form re-tripped the same class of zsh setups in v0.7.31+ — ``${...:-/bin/sh}`` parameter expansion split such that the literal ``-/bin/sh`` reached ``exec`` as a flag. Quoting ``"\$SHELL"`` and dropping the fallback is enough: sshd populates ``\$SHELL`` from the user's passwd entry in every login session, so the ``:-`` default was redundant. Tests ----- Python 1368 pass, Rust 486 pass. Did not add a stdout-EPIPE regression test — same precedent as v0.7.39 (no stderr-EPIPE test either): the timing-dependent reproduction is flaky enough to be net-negative, and the pre-fix crash signature is a better future regression detector than a brittle harness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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"}
|
||||
|
||||
12
rust/Cargo.lock
generated
12
rust/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
version = "0.7.41"
|
||||
version = "0.7.42"
|
||||
authors = ["Myeongseon Choi <key262yek@gmail.com>"]
|
||||
repository = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||
homepage = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ``</dev/tty`` redirect
|
||||
# prefix was dropped — it confused interactive zsh on some macOS →
|
||||
# Linux setups (``zsh: bad option: -/``).
|
||||
# Linux setups (``zsh: bad option: -/``). The ``${SHELL:-/bin/sh}``
|
||||
# default form re-tripped the same class of zsh setups in v0.7.31+
|
||||
# (``zsh:1: unknown exec flag -/``); ``"$SHELL"`` quoted is enough
|
||||
# because sshd populates ``$SHELL`` from passwd in every login.
|
||||
assert args["cmd"] == [
|
||||
"ssh",
|
||||
"-t",
|
||||
"prod",
|
||||
"cd /srv/app; exec ${SHELL:-/bin/sh} -il",
|
||||
'cd /srv/app; exec "$SHELL" -il',
|
||||
]
|
||||
# ``auto_close=False`` so an unexpected shell exit (dotfile error,
|
||||
# missing remote root, SSH drop) keeps the pane visible long enough
|
||||
|
||||
Reference in New Issue
Block a user