Commit 87cc98f

Eric Bower  ·  2026-03-03 20:32:47 -0500 EST
parent d12b7cc
fix(run): stdin regression with ZMX_TASK_COMPLETED

We were not actually running the command sent into stdin properly.

Closes: https://github.com/neurosnap/zmx/issues/71
1 files changed,  +29, -27
+29, -27
......@@ -445,18 +445,6 @@ pub fn main() !void {
445445 while (args.next()) |arg| {
446446 try cmd_args_raw.append(alloc, arg);
447447 }
448- var cmd_args = try cmd_args_raw.clone(alloc);
449- defer cmd_args.deinit(alloc);
450-
451- const shell = detectShell();
452- // add a task completed marker so we know when the cmd is finished
453- // we also capture the exit status
454- if (std.mem.eql(u8, std.fs.path.basename(shell), "fish")) {
455- // fish has special handling for capturing exit status
456- try cmd_args.append(alloc, "; echo ZMX_TASK_COMPLETED:$status");
457- } else {
458- try cmd_args.append(alloc, "; echo ZMX_TASK_COMPLETED:$?");
459- }
460448 const clients = try std.ArrayList(*Client).initCapacity(alloc, 10);
461449
462450 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
......@@ -480,7 +468,7 @@ pub fn main() !void {
480468 };
481469 daemon.socket_path = try getSocketPath(alloc, cfg.socket_dir, sesh);
482470 std.log.info("socket path={s}", .{daemon.socket_path});
483- return run(&daemon, cmd_args.items);
471+ return run(&daemon, cmd_args_raw.items);
484472 } else if (std.mem.eql(u8, cmd, "wait") or std.mem.eql(u8, cmd, "w")) {
485473 var args_raw: std.ArrayList([]const u8) = .empty;
486474 defer {
......@@ -1055,6 +1043,10 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
10551043 var buf: [4096]u8 = undefined;
10561044 var w = std.fs.File.stdout().writer(&buf);
10571045
1046+ var cmd_to_send: ?[]const u8 = null;
1047+ var allocated_cmd: ?[]u8 = null;
1048+ defer if (allocated_cmd) |cmd| alloc.free(cmd);
1049+
10581050 const result = try ensureSession(daemon);
10591051 if (result.is_daemon) return;
10601052
......@@ -1063,9 +1055,16 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
10631055 try w.interface.flush();
10641056 }
10651057
1066- var cmd_to_send: ?[]const u8 = null;
1067- var allocated_cmd: ?[]u8 = null;
1068- defer if (allocated_cmd) |cmd| alloc.free(cmd);
1058+ const shell = detectShell();
1059+ const shell_basename = std.fs.path.basename(shell);
1060+ const inline_task_marker = if (std.mem.eql(u8, shell_basename, "fish"))
1061+ "; echo ZMX_TASK_COMPLETED:$status"
1062+ else
1063+ "; echo ZMX_TASK_COMPLETED:$?";
1064+ const stdin_task_marker = if (std.mem.eql(u8, shell_basename, "fish"))
1065+ "echo ZMX_TASK_COMPLETED:$status"
1066+ else
1067+ "echo ZMX_TASK_COMPLETED:$?";
10691068
10701069 if (command_args.len > 0) {
10711070 var parts: std.ArrayList([]const u8) = .empty;
......@@ -1075,11 +1074,8 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
10751074 }
10761075
10771076 var total_len: usize = 0;
1078- for (command_args, 0..) |arg, i| {
1079- // Last arg is the sentinel (e.g. "; echo ZMX_TASK_COMPLETED:$?")
1080- // which must not be quoted so the shell interprets it.
1081- const is_last = i == command_args.len - 1;
1082- if (!is_last and shellNeedsQuoting(arg)) {
1077+ for (command_args) |arg| {
1078+ if (shellNeedsQuoting(arg)) {
10831079 const quoted = try shellQuote(alloc, arg);
10841080 try parts.append(alloc, quoted);
10851081 total_len += quoted.len + 1;
......@@ -1090,20 +1086,22 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
10901086 }
10911087 }
10921088
1089+ total_len += inline_task_marker.len + 1;
1090+
10931091 const cmd_buf = try alloc.alloc(u8, total_len);
10941092 allocated_cmd = cmd_buf;
10951093
10961094 var offset: usize = 0;
1097- for (parts.items, 0..) |part, i| {
1095+ for (parts.items) |part| {
10981096 @memcpy(cmd_buf[offset .. offset + part.len], part);
10991097 offset += part.len;
1100- if (i < parts.items.len - 1) {
1101- cmd_buf[offset] = ' ';
1102- } else {
1103- cmd_buf[offset] = '\n';
1104- }
1098+ cmd_buf[offset] = ' ';
11051099 offset += 1;
11061100 }
1101+
1102+ @memcpy(cmd_buf[offset .. offset + inline_task_marker.len], inline_task_marker);
1103+ offset += inline_task_marker.len;
1104+ cmd_buf[offset] = '\n';
11071105 cmd_to_send = cmd_buf;
11081106 } else {
11091107 const stdin_fd = posix.STDIN_FILENO;
......@@ -1126,6 +1124,10 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
11261124 if (needs_newline) {
11271125 try stdin_buf.append(alloc, '\n');
11281126 }
1127+
1128+ try stdin_buf.appendSlice(alloc, stdin_task_marker);
1129+ try stdin_buf.append(alloc, '\n');
1130+
11291131 cmd_to_send = try alloc.dupe(u8, stdin_buf.items);
11301132 allocated_cmd = @constCast(cmd_to_send.?);
11311133 }