repos / zmx

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

commit
efd35ac
parent
a74702a
author
ikma
date
2026-03-28 02:09:14 -0400 EDT
fix(daemon): close inherited file descriptors after fork

After the daemon forks and re-initializes its own log file, close all
file descriptors from 3 to 63 (excluding server_sock_fd). These FDs
are inherited from the parent process and are not needed by the daemon.

Without this fix, test harnesses like bats hang indefinitely. Bats uses
FDs 3+ internally and waits for them to close before exiting. Since zmx
forks a long-lived daemon that inherits these FDs, bats never sees them
close and blocks forever.

The close happens after log_system.deinit()/init() to avoid closing the
parent's log FD before it's properly released, and before spawnPty() so
the PTY FD (allocated after this point) is not affected.

Uses std.c.close() (raw libc) instead of std.posix.close() to avoid
panicking on already-closed or invalid FDs.
1 files changed,  +21, -0
M src/main.zig
+21, -0
 1@@ -694,6 +694,27 @@ const Daemon = struct {
 2                 _ = try posix.setsid();
 3 
 4                 log_system.deinit();
 5+
 6+                // Close file descriptors inherited from the parent that the
 7+                // daemon doesn't need. This prevents test harnesses (like
 8+                // bats) from hanging — they wait for their internal FDs (3+)
 9+                // to close before exiting.
10+                //
11+                // Must run BEFORE log_system.init() — otherwise the new log
12+                // FD gets closed, and spawnPty() reuses that FD number for
13+                // the PTY master, causing log writes to leak into the terminal.
14+                //
15+                // Skip server_sock_fd (needed for IPC) and dir.fd (needed to
16+                // delete the socket file on shutdown).
17+                {
18+                    const dir_fd = @as(i32, @intCast(dir.fd));
19+                    var fd: i32 = 3;
20+                    while (fd < 64) : (fd += 1) {
21+                        if (fd == server_sock_fd or fd == dir_fd) continue;
22+                        _ = std.c.close(fd);
23+                    }
24+                }
25+
26                 const session_log_name = try std.fmt.allocPrint(
27                     self.alloc,
28                     "{s}.log",