7e20404d
docs: the Darwin spellings and the claims that had drifted past them
a73x 2026-09-04 10:16
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -33,7 +33,7 @@ Files are large and comment-dense (~37% of Zig bytes are whole `//` lines). Read | |||
| 33 | costs ~800k tokens; every token stays in context and is re-billed each turn. | 33 | costs ~800k tokens; every token stays in context and is re-billed each turn. |
| 34 | 34 | ||
| 35 | - **Never `cat` these:** `src/server/server.zig` (3.3k lines, ~31k tok; its tests are | 35 | - **Never `cat` these:** `src/server/server.zig` (3.3k lines, ~31k tok; its tests are |
| 36 | siblings in `src/server/`, none over 1.7k; the agent relay and the | 36 | siblings in `src/server/`, none over 1.8k; the agent relay and the |
| 37 | session table are `src/server/server_agent.zig` and `src/server/server_sessions.zig`), | 37 | session table are `src/server/server_agent.zig` and `src/server/server_sessions.zig`), |
| 38 | `src/tui/interact.zig` (4.8k), | 38 | `src/tui/interact.zig` (4.8k), |
| 39 | `docs/decisions.md` (8.7k). `test/e2e.sh` is a 188-line runner now; the | 39 | `docs/decisions.md` (8.7k). `test/e2e.sh` is a 188-line runner now; the |
| @@ -322,15 +322,18 @@ own. Test fixtures in `test/`: | |||
| 322 | local client's entry runs `mux d start -d --sock PATH` with its own fd 2 | 322 | local client's entry runs `mux d start -d --sock PATH` with its own fd 2 |
| 323 | lent to it, so the daemon's refusals reach the user in the daemon's own | 323 | lent to it, so the daemon's refusals reach the user in the daemon's own |
| 324 | words. The fork execs `spawn.selfExe` — the running image, | 324 | words. The fork execs `spawn.selfExe` — the running image, |
| 325 | `/proc/self/exe` read THROUGH to the file it names — with argv | 325 | `/proc/self/exe` read THROUGH to the file it names on Linux and |
| 326 | `std.fs.selfExePath` on Darwin, which has no such link — with argv | ||
| 326 | `mux d start …`. No `execvp`, no PATH walk, so an auto-start can only run | 327 | `mux d start …`. No `execvp`, no PATH walk, so an auto-start can only run |
| 327 | the binary that is already running. (It used to exec a `muxd` off PATH, and an e2e leg whose daemon | 328 | the binary that is already running. (It used to exec a `muxd` off PATH, and an e2e leg whose daemon |
| 328 | had died graded an installed v0.0.1-10 with no agent code in it.) The | 329 | had died graded an installed v0.0.1-10 with no agent code in it.) The |
| 329 | resolution is not cosmetic: `comm` is the basename of the filename handed | 330 | resolution is not cosmetic: `comm` is the basename of the filename handed |
| 330 | to execve, so exec'ing the link itself names every daemon `exe` and hides | 331 | to execve, so exec'ing the link itself names every daemon `exe` and hides |
| 331 | it from `pgrep mux` and `killall mux`. Both pins are in `e2e_03_side` — | 332 | it from `pgrep mux` and `killall mux`. Both pins are in `e2e_03_side`, and |
| 332 | `readlink /proc/PID/exe` for the image, `/proc/PID/comm` for the name, | 333 | they ask through `test/os_oracle.sh` (`pid_exe` for the image, `pid_comm` |
| 333 | and `/proc/PID/cmdline` for the argv the attach may say and no more. | 334 | for the name, `pid_args` for the argv the attach may say and no more) — |
| 335 | which is `readlink /proc/PID/exe` and the two other `/proc` reads on | ||
| 336 | Linux, and `ps` plus `lsof` on Darwin, where no such directory exists. | ||
| 334 | The detached child's log is APPENDED: one xdg log serves every socket on | 337 | The detached child's log is APPENDED: one xdg log serves every socket on |
| 335 | the box, so truncating would zero a daemon still writing to it. | 338 | the box, so truncating would zero a daemon still writing to it. |
| 336 | Running the daemon IN the fork instead was tried and crashes every Debug | 339 | Running the daemon IN the fork instead was tried and crashes every Debug |
| @@ -374,7 +377,8 @@ own. Test fixtures in `test/`: | |||
| 374 | the version rule, manifest and serving check stay daemon-side and are | 377 | the version rule, manifest and serving check stay daemon-side and are |
| 375 | never reimplemented client-side. No daemon on the box is install-only | 378 | never reimplemented client-side. No daemon on the box is install-only |
| 376 | success; no mux on the box is a refusal, because a push replaces an | 379 | success; no mux on the box is a refusal, because a push replaces an |
| 377 | install rather than inventing one. The manifest memfd carries only | 380 | install rather than inventing one. The manifest carrier (a memfd on Linux, |
| 381 | an unlinked `mkstemp` file at 0600 on Darwin) carries only | ||
| 378 | what cannot be rebuilt (pty fd + child pid, the VT dump and title, the | 382 | what cannot be rebuilt (pty fd + child pid, the VT dump and title, the |
| 379 | tracker, the QUIC arm's key bytes, the cumulative counters); scrollback, | 383 | tracker, the QUIC arm's key bytes, the cumulative counters); scrollback, |
| 380 | clients, delta trackers, agent channels and per-connection QUIC state are | 384 | clients, delta trackers, agent channels and per-connection QUIC state are |
README.md
| Old | New | ||
|---|---|---|---|
| @@ -716,7 +716,10 @@ Intel build, no universal binary, and no way to cross-compile the Mac | |||
| 716 | binary from Linux, so a Mac binary is made on a Mac. | 716 | binary from Linux, so a Mac binary is made on a Mac. |
| 717 | 717 | ||
| 718 | You need Xcode's Command Line Tools for the SDK and `cmake` for the QUIC | 718 | You need Xcode's Command Line Tools for the SDK and `cmake` for the QUIC |
| 719 | dependencies: | 719 | dependencies, and the pinned toolchain in the same place a Linux checkout |
| 720 | puts it: unpack the aarch64 Zig 0.15.2 tarball so that the binary is at | ||
| 721 | `deps/zig/zig`. The Makefile spells that path and nothing else, so a | ||
| 722 | system `zig` is neither used nor enough. | ||
| 720 | 723 | ||
| 721 | ```sh | 724 | ```sh |
| 722 | xcode-select --install | 725 | xcode-select --install |
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -8472,7 +8472,10 @@ because tmux asks only that no other-user bit is set and this asks for | |||
| 8472 | exactly 0700. Linux keeps NO fallback: a guess cannot make two binaries | 8472 | exactly 0700. Linux keeps NO fallback: a guess cannot make two binaries |
| 8473 | agree on one daemon, so the caller names it with `--sock`. | 8473 | agree on one daemon, so the caller names it with `--sock`. |
| 8474 | 8474 | ||
| 8475 | **Four operations are a different mechanism, not a different spelling.** | 8475 | **Four operations are a different mechanism, not a different spelling**, and |
| 8476 | the `forkDetached` pin is a fifth difference of the same kind in the test | ||
| 8477 | rather than in the arm. The four are `closeFrom`, `anonFd`, `sendNoSig` and | ||
| 8478 | `peerCred`, which is the count and the list the step-3 entry above gives. | ||
| 8476 | `closeFrom` walks the fd table one close at a time from the floor to | 8479 | `closeFrom` walks the fd table one close at a time from the floor to |
| 8477 | `getdtablesize()`, because Darwin has no `close_range`; that is a few | 8480 | `getdtablesize()`, because Darwin has no `close_range`; that is a few |
| 8478 | hundred cheap EBADFs once per session start, between fork and exec, so | 8481 | hundred cheap EBADFs once per session start, between fork and exec, so |
| @@ -8490,10 +8493,13 @@ property of the socket rather than the send, and a REFUSED set is the | |||
| 8490 | answer: Darwin's `sosetopt` rejects every option with EINVAL once a socket | 8493 | answer: Darwin's `sosetopt` rejects every option with EINVAL once a socket |
| 8491 | is shut down both ways, which is exactly the hung-up state, so the arm | 8494 | is shut down both ways, which is exactly the hung-up state, so the arm |
| 8492 | reads that EINVAL as the peer being gone and returns `error.BrokenPipe`. | 8495 | reads that EINVAL as the peer being gone and returns `error.BrokenPipe`. |
| 8493 | Fourth, the `forkDetached` test asks `getsid(2)` rather than `ps`: macOS's | 8496 | `peerCred` needs two calls: Darwin's `LOCAL_PEERCRED` answers a `struct |
| 8494 | `ps` has no `sid` column and its `sess` column is a kernel address that | 8497 | xucred` with no pid in it, so the uid comes from `getpeereid` and the pid |
| 8495 | reads 0 for anyone but root, so the natural port of the Linux pin compared | 8498 | from `LOCAL_PEERPID`. And the fifth, in the test: the `forkDetached` pin |
| 8496 | 0 against 0 and passed whatever the child did. | 8499 | asks `getsid(2)` rather than `ps`, because macOS's `ps` has no `sid` column |
| 8500 | and its `sess` column is a kernel address that reads 0 for anyone but root, | ||
| 8501 | so the natural port of the Linux pin compared 0 against 0 and passed | ||
| 8502 | whatever the child did. | ||
| 8497 | 8503 | ||
| 8498 | **A Darwin panic the gate found, and the shape of its fix.** Attaching a | 8504 | **A Darwin panic the gate found, and the shape of its fix.** Attaching a |
| 8499 | Mac client to a Linux daemon crashed with `attempt to unwrap error: | 8505 | Mac client to a Linux daemon crashed with `attempt to unwrap error: |
| @@ -8672,8 +8678,10 @@ made and re-run green on both OSes: | |||
| 8672 | the Mac, three out of three. So whatever discards it is on the far side | 8678 | the Mac, three out of three. So whatever discards it is on the far side |
| 8673 | of the pty and not an input byte mux drops — by elimination, since less's | 8679 | of the pty and not an input byte mux drops — by elimination, since less's |
| 8674 | own source was not read; a `TCSAFLUSH` on re-entering raw mode is what | 8680 | own source was not read; a `TCSAFLUSH` on re-entering raw mode is what |
| 8675 | behaves this way and is the guess. Nothing in the product changes and the | 8681 | behaves this way and is the guess. It stays OPEN, which is the word the |
| 8676 | leg no longer types into the pager at all. | 8682 | README and the ledger use for it: elimination says where the byte is not |
| 8683 | lost, and nobody has yet said where it is. Nothing in the product changes | ||
| 8684 | and the leg no longer types into the pager at all. | ||
| 8677 | Recorded because the shape — one byte, first write, one OS — is exactly | 8685 | Recorded because the shape — one byte, first write, one OS — is exactly |
| 8678 | what a real input bug would look like, and the next person to see it | 8686 | what a real input bug would look like, and the next person to see it |
| 8679 | should know these two probes exist. | 8687 | should know these two probes exist. |
| @@ -8691,9 +8699,11 @@ made and re-run green on both OSes: | |||
| 8691 | because its detector required the heredoc's delimiter to end the line and | 8699 | because its detector required the heredoc's delimiter to end the line and |
| 8692 | that opener has a trailing space after it; the replacement comment | 8700 | that opener has a trailing space after it; the replacement comment |
| 8693 | written for the third site had a backtick in it too. The rule is written | 8701 | written for the third site had a backtick in it too. The rule is written |
| 8694 | above each of the three heredocs, which is all that gates it. A lint | 8702 | above each of the three heredocs. A lint is better and it is small — |
| 8695 | would be better and it is small — match the opener anywhere on the line, | 8703 | match the opener anywhere on the line, skip a quoted delimiter, and refuse |
| 8696 | skip a quoted delimiter, and refuse a backtick in the body. | 8704 | a backtick in the body — so `test/bans.sh` has one, wired into `make |
| 8705 | check`, with a planted positive in both opener spellings and a quoted | ||
| 8706 | delimiter as the negative. The comments stay; the lint is what gates it. | ||
| 8697 | 8707 | ||
| 8698 | **Two groups stay RED, and both are red BY DESIGN — the same design.** | 8708 | **Two groups stay RED, and both are red BY DESIGN — the same design.** |
| 8699 | `sockpath.runtimeDir` has no fallback on Linux and falls back to | 8709 | `sockpath.runtimeDir` has no fallback on Linux and falls back to |
test/bans.sh
| Old | New | ||
|---|---|---|---|
| @@ -118,9 +118,11 @@ must_skip src/server 'test "bans probe" { | |||
| 118 | # ---- the shell rule: no backtick inside an UNQUOTED heredoc ------------- | 118 | # ---- the shell rule: no backtick inside an UNQUOTED heredoc ------------- |
| 119 | # | 119 | # |
| 120 | # In an unquoted heredoc the shell runs a backquoted word as a command | 120 | # In an unquoted heredoc the shell runs a backquoted word as a command |
| 121 | # substitution wherever it stands, comments included. Every ptyclient script | 121 | # substitution wherever it stands, comments included. Most ptyclient scripts |
| 122 | # in this suite is an unquoted heredoc — they interpolate a fingerprint or a | 122 | # in this suite are QUOTED heredocs and safe; the ones that interpolate a |
| 123 | # port — and this repo quotes a key or a verb in backticks everywhere else, | 123 | # fingerprint or a port have to leave the delimiter bare, and those are the |
| 124 | # ones this rule is for. This repo quotes a key or a verb in backticks | ||
| 125 | # everywhere else, | ||
| 124 | # so writing one into such a comment is the natural thing to do and it is a | 126 | # so writing one into such a comment is the natural thing to do and it is a |
| 125 | # trap. There IS an expect program on macOS: the shell starts it, it reads | 127 | # trap. There IS an expect program on macOS: the shell starts it, it reads |
| 126 | # the rest of the heredoc as its own script, and the group hangs until its | 128 | # the rest of the heredoc as its own script, and the group hangs until its |
| @@ -180,12 +182,14 @@ SHPROBE="$ROOT/test/zz_bans_heredoc_$$.sh" | |||
| 180 | # contain the very shape it refuses — and the trailing space after the | 182 | # contain the very shape it refuses — and the trailing space after the |
| 181 | # first opener is half of what is being pinned. | 183 | # first opener is half of what is being pinned. |
| 182 | # | 184 | # |
| 183 | # The opener is BUILT rather than written, so that no line of this file | 185 | # The FIXTURE's opener is BUILT rather than written. The sweep's opener |
| 184 | # holds the two angle brackets next to each other. Spelling them here would | 186 | # detector spares a quoted delimiter, and it sees a quote right after the |
| 185 | # make the negative sweep below read this script's own fixture as a heredoc | 187 | # two angle brackets in the awk program at the top of `heredoc_backticks`, |
| 186 | # and report its three backticks, and the only ways out of that are an | 188 | # so those literals do not fool it — but a bare opener written out here |
| 187 | # exception for this file or a rule that stops covering it. Neither is | 189 | # would make it read this script's own fixture as a heredoc and report the |
| 188 | # worth a pair of characters. | 190 | # three backticks planted below. The only ways out of that are an exception |
| 191 | # for this file or a rule that stops covering it. Neither is worth a pair of | ||
| 192 | # characters. | ||
| 189 | _a='<' | 193 | _a='<' |
| 190 | _hd="$_a$_a" | 194 | _hd="$_a$_a" |
| 191 | printf '%s\n' \ | 195 | printf '%s\n' \ |
test/e2e_11_select.sh
| Old | New | ||
|---|---|---|---|
| @@ -377,7 +377,7 @@ kill -STOP "$AGENT48PID" | |||
| 377 | set +e | 377 | set +e |
| 378 | # Assembled rather than written as one heredoc, because ONE of its lines | 378 | # Assembled rather than written as one heredoc, because ONE of its lines |
| 379 | # needs a value from THIS shell: the session times its own ssh-add, and the | 379 | # needs a value from THIS shell: the session times its own ssh-add, and the |
| 380 | # clock that does it is `date +%s%N` on one OS and `gdate +%s%N` on the | 380 | # clock that does it is `date +%s%N` on one OS and a perl one-liner on the |
| 381 | # other, so the text comes from now_ms_snippet. Both halves stay QUOTED | 381 | # other, so the text comes from now_ms_snippet. Both halves stay QUOTED |
| 382 | # heredocs — the lines around the seam spell `$?`, `$R` and a backticked | 382 | # heredocs — the lines around the seam spell `$?`, `$R` and a backticked |
| 383 | # `d` that all have to reach the SESSION as characters rather than be spent | 383 | # `d` that all have to reach the SESSION as characters rather than be spent |
test/os_oracle.sh
| Old | New | ||
|---|---|---|---|
| @@ -315,11 +315,16 @@ eval "$(now_ms_snippet)" | |||
| 315 | # Almost every caller in this tree runs `timeout <seconds> CMD` in the | 315 | # Almost every caller in this tree runs `timeout <seconds> CMD` in the |
| 316 | # foreground over a single mux or ptyclient child, with no flags, and none | 316 | # foreground over a single mux or ptyclient child, with no flags, and none |
| 317 | # of the three differences can reach one. The exceptions, and why they are | 317 | # of the three differences can reach one. The exceptions, and why they are |
| 318 | # still safe: e2e_01_boot.sh backgrounds one client and reaps it by pid | 318 | # still safe: e2e_01_boot.sh and e2e_10_agent.sh each background one |
| 319 | # rather than by signalling the wrapper; e2e_05_session.sh wraps a wsclient, | 319 | # timeout-wrapped client, and both reap it with `wait` on the wrapper's own |
| 320 | # which is one child like the others; and test/vm.sh, test/mac.sh and | 320 | # pid rather than by signalling it — the agent group's `defer_kill` on that |
| 321 | # test/xos.sh wrap ssh, but those three drivers run on the Linux side of a | 321 | # pid runs only when the leg has already failed, and on this arm it would |
| 322 | # gate, where `timeout` is the GNU binary and this arm is never defined. | 322 | # leave the child to finish its own budget, which is a slower cleanup and |
| 323 | # not a different verdict; e2e_05_session.sh, e2e_06_web.sh and | ||
| 324 | # e2e_13_birth.sh wrap a wsclient, which is one child like the others; and | ||
| 325 | # test/vm.sh, test/mac.sh and test/xos.sh wrap ssh, but those three drivers | ||
| 326 | # run on the Linux side of a gate, where `timeout` is the GNU binary and | ||
| 327 | # this arm is never defined. | ||
| 323 | if command -v timeout >/dev/null 2>&1; then | 328 | if command -v timeout >/dev/null 2>&1; then |
| 324 | : | 329 | : |
| 325 | elif command -v gtimeout >/dev/null 2>&1; then | 330 | elif command -v gtimeout >/dev/null 2>&1; then |