Commit a25e2fe
Eric Bower
·
2026-05-20 22:21:35 -0400 EDT
parent d5c6926
refactor(run): marker for heredoc For most commands we can append `; $?` except for heredoc where the marker needs to be on its own line.
1 files changed,
+28,
-14
+28,
-14
1@@ -1140,17 +1140,33 @@ const Daemon = struct {
2
3 const cmd = payload;
4
5- // Daemon appends the task marker so we know when a task is done with
6- // exit status
7- const marker = "echo ZMX_TASK_COMPLETED:$?\r";
8-
9+ // Redirect stdin from /dev/null to prevent interactive programs
10+ // (pagers, editors, prompts) from blocking the task. Programs that
11+ // detect a TTY and open a pager (e.g. git, man) or read stdin
12+ // (e.g. cat, head) will receive EOF instead of blocking. This
13+ // matches the behavior of CI runners and `tmux send-keys`.
14+ //
15+ // Commands that legitimately need stdin should use `zmx send`
16+ // instead, or pipe data directly: `echo data | zmx run dev cat`.
17+ // Here-documents still work because the shell processes the
18+ // here-document before applying the /dev/null redirection.
19+ const stdin_redirect = "< /dev/null ";
20+
21+ // Chain the exit marker with `;` on the same line. `$?` captures the
22+ // exit code of the command (not the `;`). The sole exception is when
23+ // the command contains a heredoc (`<<`) — the delimiter must be alone
24+ // on its line, so the marker goes on the next line instead.
25+ const single_line_marker = "; echo ZMX_TASK_COMPLETED:$?\r";
26+ const heredoc_marker = "\r\necho ZMX_TASK_COMPLETED:$?\r";
27+ const uses_heredoc = std.mem.indexOf(u8, cmd, "<<") != null;
28+
29+ self.queuePtyInput(stdin_redirect);
30 if (cmd.len > 0 and cmd[cmd.len - 1] == '\r') {
31 self.queuePtyInput(cmd[0 .. cmd.len - 1]);
32 } else {
33 self.queuePtyInput(cmd);
34 }
35- self.queuePtyInput("\r");
36- self.queuePtyInput(marker);
37+ self.queuePtyInput(if (uses_heredoc) heredoc_marker else single_line_marker);
38
39 try ipc.appendMessage(self.alloc, &client.write_buf, .Ack, "");
40 client.has_pending_output = true;
41@@ -1282,13 +1298,10 @@ fn help() !void {
42 \\ Commands run inside a PTY using bash
43 \\ Commands are passed as-is: do not wrap in quotes.
44 \\ Commands run sequentially: do not send multiple in parallel.
45- \\ Avoid interactive programs (pagers, editors, prompts): they hang.
46- \\
47- \\ If the command hangs, send Ctrl+C to recover:
48- \\ zmx run <session> $(printf '\x03')
49- \\
50- \\ If the command hangs, print the history to see the error:
51- \\ zmx history <session> | tail -100
52+ \\ Stdin is redirected from /dev/null to prevent interactive programs
53+ \\ (pagers, editors, prompts) from blocking. Use `zmx send` for
54+ \\ commands that need user input, or pipe data directly:
55+ \\ echo "data" | zmx run dev cat
56 \\
57 \\ `-d` will detach from the calling terminal. Use `wait` to track
58 \\ its status.
59@@ -1297,7 +1310,8 @@ fn help() !void {
60 \\ zmx run dev ls
61 \\ zmx run dev zig build
62 \\ zmx run dev grep -r TODO src
63- \\ zmx run dev git -c core.pager=cat diff
64+ \\ zmx run dev git log --oneline # pager won't block
65+ \\ echo "hello" | zmx run dev cat # piped stdin still works
66 \\
67 \\ zmx run dev -d sleep 10
68 \\ zmx wait dev