a73x

89de963b

fix: the spawn is an exec, not a fork — four comments still said fork

a73x   2026-08-29 02:18

Commit message
fix: the spawn is an exec, not a fork — four comments still said fork

fork-into-run was tried and crashed (a forked child's first
DebugAllocator trace panics in MemoryAccessor), so the child execs this
same image with argv `mux d run …`. Four comments outlived that: the
daemon main's doc, the build's one-binary note, the e2e PATH note and
coverage.sh's tracing note. coverage.sh's conclusion still holds —
ptrace follows an exec — but for a reason it did not state.

`mux d`'s main takes argv because the dispatcher has eaten the mode
word, which is the reason that survives.

build.zig
Old New
@@ -924,9 +924,10 @@ pub fn build(b: *std.Build) void {
924 924
925 // ONE product binary. The four mains are modules under it, reached by 925 // ONE product binary. The four mains are modules under it, reached by
926 // the mode word rather than by four names on PATH — which is also what 926 // the mode word rather than by four names on PATH — which is also what
927 // lets the client's auto-start fork into the daemon instead of hunting 927 // lets the client's auto-start exec this same image with argv
928 // for a sibling. The fixtures below stay separate: they stand in for 928 // `mux d run …` (spawn.zig) instead of hunting PATH for a sibling. The
929 // users, not for the product. 929 // fixtures below stay separate: they stand in for users, not for the
930 // product.
930 const mux_exe = b.addExecutable(.{ .name = "mux", .root_module = mux_mod }); 931 const mux_exe = b.addExecutable(.{ .name = "mux", .root_module = mux_mod });
931 // Zig 0.15's self-hosted x86_64 linker can't handle the .sframe 932 // Zig 0.15's self-hosted x86_64 linker can't handle the .sframe
932 // sections emitted by gcc >= 16 crt1.o on this system; LLD can. 933 // sections emitted by gcc >= 16 crt1.o on this system; LLD can.
src/cli/main.zig
Old New
@@ -282,9 +282,9 @@ fn parseBindAddr(s: []const u8) !std.net.Address {
282 return std.net.Address.parseIp(hp.host, hp.port); 282 return std.net.Address.parseIp(hp.host, hp.port);
283 } 283 }
284 284
285 /// `mux d`. argv arrives from the dispatcher rather than from 285 /// `mux d`. argv arrives from the dispatcher, which has already eaten the
286 /// `argsAlloc`, because the local auto-start reaches this entry by fork 286 /// mode word, rather than from `argsAlloc`: one binary, four mains, and
287 /// instead of exec (spawn.zig) and a forked child has no argv of its own. 287 /// only the dispatcher knows where each mode's arguments start.
288 pub fn main(args: []const [:0]const u8) !u8 { 288 pub fn main(args: []const [:0]const u8) !u8 {
289 var gpa: std.heap.DebugAllocator(.{}) = .init; 289 var gpa: std.heap.DebugAllocator(.{}) = .init;
290 defer if (gpa.deinit() == .leak) 290 defer if (gpa.deinit() == .leak)
test/coverage.sh
Old New
@@ -75,7 +75,8 @@ mkdir -p "$SHIM" "$RAW"
75 # would otherwise run its client traced in silence. The client mode is NOT 75 # would otherwise run its client traced in silence. The client mode is NOT
76 # in the default set, and that is a limitation rather than a preference: a 76 # in the default set, and that is a limitation rather than a preference: a
77 # traced client that auto-starts a daemon can never exit. The daemon is now 77 # traced client that auto-starts a daemon can never exit. The daemon is now
78 # a FORK of the client (spawn.zig) rather than an exec of a sibling, so it 78 # an exec of THIS image from a fork of the client (spawn.zig) rather than of
79 # a sibling on PATH, and ptrace follows an exec, so it
79 # is traced whenever the client is, stays traced after it reparents to 80 # is traced whenever the client is, stays traced after it reparents to
80 # init, and kcov waits for its last traced descendant — a process designed 81 # init, and kcov waits for its last traced descendant — a process designed
81 # never to end. The auto-start and `--via` legs wedge on this, and a 82 # never to end. The auto-start and `--via` legs wedge on this, and a
test/e2e.sh
Old New
@@ -23,9 +23,9 @@ WSCLIENT="$6"
23 # The ssh recipes a handoff leg drives name `mux` and let the remote's PATH 23 # The ssh recipes a handoff leg drives name `mux` and let the remote's PATH
24 # find it, so the build's own bin directory goes in front of the shims that 24 # find it, so the build's own bin directory goes in front of the shims that
25 # stand in for a remote. The client's LOCAL auto-start no longer reads PATH 25 # stand in for a remote. The client's LOCAL auto-start no longer reads PATH
26 # at all — it forks into this binary's own daemon (spawn.zig) — so a leg 26 # at all — it execs this same binary with argv `mux d run …` (spawn.zig) —
27 # that forgets its `mux d run &` can no longer silently grade an installed 27 # so a leg that forgets its `mux d run &` can no longer silently grade an
28 # release, which it did against a v0.0.1-10 with no agent code in it. 28 # installed release, which it did against a v0.0.1-10 with no agent code.
29 PATH="$(cd "$(dirname "$MUX")" && pwd):$PATH" 29 PATH="$(cd "$(dirname "$MUX")" && pwd):$PATH"
30 export PATH 30 export PATH
31 31