repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

commit
e17899d
parent
377c062
author
Eric Bower
date
2026-01-21 10:06:30 -0500 EST
fix: properly handle killing shells and children

Shell's typically ignore SIGTERM unless there's a trap installed, but they will respond to SIGHUP.
This works fine for shells but when a user runs `zig attach editor nvim` or any other non-shell command
they might not respond to SIGHUP the way we want.  So we have implemented a multi-signal approach:

- SIGHUP
- Wait 500ms
- SIGKILL

Reference: https://github.com/shell-pool/shpool/blob/1b3b27f059f0008c0509b5170d84b86c752b1da3/libshpool/src/daemon/shell.rs#L79-L94
Reference: https://github.com/neurosnap/zmx/pull/47
1 files changed,  +10, -4
M src/main.zig
+10, -4
 1@@ -242,11 +242,18 @@ const Daemon = struct {
 2 
 3     pub fn handleKill(self: *Daemon) void {
 4         std.log.info("kill received session={s}", .{self.session_name});
 5+        self.shutdown();
 6+        // gracefully shutdown shell processes, shells tend to ignore SIGTERM so we send SIGHUP instead
 7+        //   https://www.gnu.org/software/bash/manual/html_node/Signals.html
 8         // negative pid means kill process and children
 9-        posix.kill(-self.pid, posix.SIG.TERM) catch |err| {
10-            std.log.warn("failed to send SIGTERM to pty child err={s}", .{@errorName(err)});
11+        std.log.info("sending SIGHUP session={s} pid={d}", .{ self.session_name, self.pid });
12+        posix.kill(-self.pid, posix.SIG.HUP) catch |err| {
13+            std.log.warn("failed to send SIGHUP to pty child err={s}", .{@errorName(err)});
14+        };
15+        std.Thread.sleep(500 * std.time.ns_per_ms);
16+        posix.kill(-self.pid, posix.SIG.KILL) catch |err| {
17+            std.log.warn("failed to send SIGKILL to pty child err={s}", .{@errorName(err)});
18         };
19-        self.shutdown();
20     }
21 
22     pub fn handleInfo(self: *Daemon, client: *Client) !void {
23@@ -1152,7 +1159,6 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
24                             break :clients_loop;
25                         },
26                         .Kill => {
27-                            daemon.handleKill();
28                             break :daemon_loop;
29                         },
30                         .Info => try daemon.handleInfo(client),