a73x

06046c02

docs: what the new check caught

a73x   2026-08-20 09:01

Commit message
docs: what the new check caught

Thirty codename references and two stale file citations, in twelve of the
thirty-two src modules. The manual sweep that just went through this tree did
not see any of them, which is the argument for the gate that follows this
commit rather than an argument about these particular lines.

Codenames first. "M18", "Phase 3c", "M-web Task 1" name nothing a reader can
look up from the code — the milestone list lives in docs/roadmap.md and the
reasoning in docs/decisions.md, and neither is reachable from a comment that
only says "M18". The EVENT survives the rename: pty.zig's fd leak is
"unreachable at one session per daemon, which is why the multi-session daemon
is where it surfaced", and that sentence still explains itself in a year.
Dated journals keep their codenames; they are history, and history is what
they are for.

Two file citations named a file this repo does not have:

  protocol.zig  stream_terminal.zig      -> terminal/stream_terminal.zig
  proxy.zig     Zig's start.zig          -> Zig's std/start.zig

Both are foreign — ghostty-vt and the Zig stdlib — and client_core.zig already
writes that kind of reference with its tree in front of it
(osc/parsers/clipboard_operation.zig). Making the other two match is not
punctuation: the prefix is how a reader tells "not in this repo" from "renamed
last week", and it is how the next commit's check tells them apart too.

Comments only. No behaviour changes.

src/client.zig
Old New
@@ -7,8 +7,8 @@
7 //! backoff schedule, the wall-file record an attach earns, and the pure 7 //! backoff schedule, the wall-file record an attach earns, and the pure
8 //! answers a `Ctrl-\ c`/`n`/`p` needs out of a `sessions_reply`. 8 //! answers a `Ctrl-\ c`/`n`/`p` needs out of a `sessions_reply`.
9 //! 9 //!
10 //! What it no longer owns is a SESSION. Phase 3c of the wall-home-screen 10 //! What it no longer owns is a SESSION. The wall-home-screen work
11 //! spec converged the two interaction loops that existed here and in 11 //! converged the two interaction loops that existed here and in
12 //! wallview.zig into one: `mux [TARGET]` is now a wall of one tile, entered 12 //! wallview.zig into one: `mux [TARGET]` is now a wall of one tile, entered
13 //! zoomed, so the loop that drives an `interact.Core` over a link — the 13 //! zoomed, so the loop that drives an `interact.Core` over a link — the
14 //! chord meanings, the reconnect, the terminal's lifetime, the exit code — 14 //! chord meanings, the reconnect, the terminal's lifetime, the exit code —
src/handoff.zig
Old New
@@ -9,9 +9,10 @@ const std = @import("std");
9 const xdg = @import("xdg"); 9 const xdg = @import("xdg");
10 10
11 /// The QUIC attach budget for one attempt, warm path and cold path 11 /// The QUIC attach budget for one attempt, warm path and cold path
12 /// alike — and, since M15, for a direct `quic://` dial too. Pinned from 12 /// alike — and, since the module-seam refactor, for a direct `quic://` dial
13 /// measurement on 2026-08-11 (decisions.md, M14), re-measured 2026-08-12 13 /// too. Pinned from measurement on 2026-08-11 when the ssh→QUIC handoff
14 /// (M15 Task 4) after the one fast-failure case was taught to work. 14 /// landed (decisions.md), re-measured 2026-08-12 after the one fast-failure
15 /// case was taught to work.
15 /// 16 ///
16 /// Measured: silence is what this bound is for, and silence is the 17 /// Measured: silence is what this bound is for, and silence is the
17 /// common case. A wrong PSK against a live listener ran 2037ms of a 18 /// common case. A wrong PSK against a live listener ran 2037ms of a
@@ -29,7 +30,7 @@ const xdg = @import("xdg");
29 /// 30 ///
30 /// Silence spending the budget fixes the shape of the trade, and 2000 31 /// Silence spending the budget fixes the shape of the trade, and 2000
31 /// stands. Below: a real handshake is 4ms local, 6.9ms cold attach on 32 /// stands. Below: a real handshake is 4ms local, 6.9ms cold attach on
32 /// the M8 LAN, and 234ms at 75ms RTT — 3.1x RTT, since every fresh 33 /// the LAN box, and 234ms at 75ms RTT — 3.1x RTT, since every fresh
33 /// Initial costs a Retry round trip — so 2000 covers RTT to ~645ms, 34 /// Initial costs a Retry round trip — so 2000 covers RTT to ~645ms,
34 /// past any terrestrial link. Above: with no negative caching of "UDP 35 /// past any terrestrial link. Above: with no negative caching of "UDP
35 /// blocked" by design, every fallback attach on such a network pays this 36 /// blocked" by design, every fallback attach on such a network pays this
@@ -359,7 +360,7 @@ test "announce: a key with leading zero bytes still hexes to 64 chars" {
359 360
360 test "announce: the WRITER refuses port 0, where the mistake is still local" { 361 test "announce: the WRITER refuses port 0, where the mistake is still local" {
361 // `endpoint_reply` carries 0 as a legal wire value meaning "could 362 // `endpoint_reply` carries 0 as a legal wire value meaning "could
362 // not", so Task 5 holds a u16 that may be 0 and must turn it into 363 // not", so the reader holds a u16 that may be 0 and must turn it into
363 // `endpoint none` rather than a line. Refusing at the writer means a 364 // `endpoint none` rather than a line. Refusing at the writer means a
364 // caller that forgets fails on its own line, instead of shipping a 365 // caller that forgets fails on its own line, instead of shipping a
365 // line that only fails much later, on the client, as a parse error 366 // line that only fails much later, on the client, as a parse error
src/keymap.zig
Old New
@@ -1,7 +1,8 @@
1 //! Normalized key events → VT byte sequences. The portable half of input: 1 //! Normalized key events → VT byte sequences. The portable half of input:
2 //! each shell (browser KeyboardEvent, later xkb) produces the normalized 2 //! each shell (browser KeyboardEvent, later xkb) produces the normalized
3 //! form; this module owns every byte that reaches the PTY, so the tables 3 //! form; this module owns every byte that reaches the PTY, so the tables
4 //! are unit-tested natively with no browser in the loop (M-web Task 3). 4 //! are unit-tested natively with no browser in the loop — they arrived
5 //! for the browser client and were never allowed to depend on it.
5 //! 6 //!
6 //! v1 scope (spec): printable input, control characters, arrows and nav 7 //! v1 scope (spec): printable input, control characters, arrows and nav
7 //! keys, function keys, the xterm modifier-encoded CSI variants, and 8 //! keys, function keys, the xterm modifier-encoded CSI variants, and
src/main.zig
Old New
@@ -346,7 +346,7 @@ pub fn main() !u8 {
346 .stats => return stats(alloc, sock_path), 346 .stats => return stats(alloc, sock_path),
347 .stop => return stopCmd(alloc, sock_path), 347 .stop => return stopCmd(alloc, sock_path),
348 .proxy => { 348 .proxy => {
349 // Attach auto-start (M13): the user asked for a session, not a 349 // Attach auto-start: the user asked for a session, not a
350 // daemon. Same helper and deadline as `muxd start`. Unlike it, a 350 // daemon. Same helper and deadline as `muxd start`. Unlike it, a
351 // daemon that was already there is silent: the user asked for a 351 // daemon that was already there is silent: the user asked for a
352 // session and is about to get one. 352 // session and is about to get one.
@@ -367,7 +367,8 @@ pub fn main() !u8 {
367 /// a daemon to set an environment for. 367 /// a daemon to set an environment for.
368 /// 368 ///
369 /// Opt-IN: `=1` and nothing else. It was an opt-out through the agent 369 /// Opt-IN: `=1` and nothing else. It was an opt-out through the agent
370 /// surface and M18, on the reasoning that marks are what make an exit code 370 /// surface and the multi-session daemon, on the reasoning that marks are
371 /// what make an exit code
371 /// knowable; the daily-driver reading is the opposite one, because the shim 372 /// knowable; the daily-driver reading is the opposite one, because the shim
372 /// costs a zsh user their `~/.zshenv` and displaces a bash user's DEBUG trap 373 /// costs a zsh user their `~/.zshenv` and displaces a bash user's DEBUG trap
373 /// (atuin, bash-preexec) on every session, while only `muxa` reads what it 374 /// (atuin, bash-preexec) on every session, while only `muxa` reads what it
@@ -560,7 +561,7 @@ fn oneShotQuery(
560 fn dump(alloc: std.mem.Allocator, sock_path: []const u8, vt_mode: bool, session: []const u8) !u8 { 561 fn dump(alloc: std.mem.Allocator, sock_path: []const u8, vt_mode: bool, session: []const u8) !u8 {
561 // vt byte ++ session-name tail, built by the wire module — empty is the 562 // vt byte ++ session-name tail, built by the wire module — empty is the
562 // wire's own default spelling, so a bare `muxd dump` sends exactly the 563 // wire's own default spelling, so a bare `muxd dump` sends exactly the
563 // pre-M18 one-byte payload. 564 // one-byte payload that predates session names.
564 var buf: [proto.debug_dump_max_len]u8 = undefined; 565 var buf: [proto.debug_dump_max_len]u8 = undefined;
565 const payload = proto.encodeDebugDumpNamed(&buf, vt_mode, session); 566 const payload = proto.encodeDebugDumpNamed(&buf, vt_mode, session);
566 return oneShotQuery(alloc, sock_path, "dump", .debug_dump, payload, .dump_reply); 567 return oneShotQuery(alloc, sock_path, "dump", .debug_dump, payload, .dump_reply);
src/predict.zig
Old New
@@ -43,20 +43,21 @@
43 //! once or twice per command at a normal prompt, as readline hands the 43 //! once or twice per command at a normal prompt, as readline hands the
44 //! terminal back and forth to run each command; and since every move 44 //! terminal back and forth to run each command; and since every move
45 //! re-earns display from scratch, the first couple of keystrokes after each 45 //! re-earns display from scratch, the first couple of keystrokes after each
46 //! prompt are invisible predictions. That is the conservative trade M9 46 //! prompt are invisible predictions. That is the conservative trade this
47 //! takes deliberately — per-context confidence memory is the banked polish. 47 //! module takes deliberately — per-context confidence memory is the banked
48 //! polish.
48 //! 49 //!
49 //! Memory: the overlay owns everything it holds. Predictions are copies of 50 //! Memory: the overlay owns everything it holds. Predictions are copies of
50 //! bytes, never slices into frame payloads or engine rows, and the queue is 51 //! bytes, never slices into frame payloads or engine rows, and the queue is
51 //! read back by index rather than handed out as a slice — a slice would go 52 //! read back by index rather than handed out as a slice — a slice would go
52 //! stale on the next append, which is the shape the M8 egress records call 53 //! stale on the next append, which is the shape decisions.md's egress
53 //! the UAF-that-never-crashes. 54 //! records call the UAF-that-never-crashes.
54 const std = @import("std"); 55 const std = @import("std");
55 const proto = @import("protocol"); 56 const proto = @import("protocol");
56 57
57 /// One predicted character at one place on the grid. Printable ASCII only 58 /// One predicted character at one place on the grid. Printable ASCII only,
58 /// in M9: `ch` is a byte by value, so there is nothing here that can outlive 59 /// deliberately: `ch` is a byte by value, so there is nothing here that can
59 /// what it was copied from. 60 /// outlive what it was copied from.
60 pub const Cell = struct { row: u16, col: u16, ch: u8 }; 61 pub const Cell = struct { row: u16, col: u16, ch: u8 };
61 62
62 pub const Pred = struct { 63 pub const Pred = struct {
@@ -107,7 +108,7 @@ pub const Counters = struct {
107 /// how often we were wrong, not how much was thrown away. 108 /// how often we were wrong, not how much was thrown away.
108 contradicted: u64 = 0, 109 contradicted: u64 = 0,
109 /// PER EVENT: the subset of `contradicted` where nothing ever answered 110 /// PER EVENT: the subset of `contradicted` where nothing ever answered
110 /// rather than something disagreed. Counted in both, so Task 7 can read 111 /// rather than something disagreed. Counted in both, so a reader can tell
111 /// "we guessed wrong" apart from "the application went quiet". 112 /// "we guessed wrong" apart from "the application went quiet".
112 expired: u64 = 0, 113 expired: u64 = 0,
113 /// PER PREDICTION: queued but discarded without a verdict — by a 114 /// PER PREDICTION: queued but discarded without a verdict — by a
@@ -742,7 +743,7 @@ test "a contradiction flushes the whole queue, not merely the cell that was wron
742 // be claiming agreement about a screen it had already been told it was 743 // be claiming agreement about a screen it had already been told it was
743 // wrong about. 744 // wrong about.
744 try std.testing.expectEqual(@as(u64, 0), ov.counters.confirmed); 745 try std.testing.expectEqual(@as(u64, 0), ov.counters.confirmed);
745 // A refutation is not an expiry, and Task 7 reads these apart. 746 // A refutation is not an expiry, and the counters read these apart.
746 try std.testing.expectEqual(@as(u64, 0), ov.counters.expired); 747 try std.testing.expectEqual(@as(u64, 0), ov.counters.expired);
747 } 748 }
748 749
src/protocol.zig
Old New
@@ -703,8 +703,8 @@ pub const clipboard_base64_max: usize = 64 * 1024;
703 /// 703 ///
704 /// 1024 is not an independent choice — it is the length ghostty's own 704 /// 1024 is not an independent choice — it is the length ghostty's own
705 /// handler already truncates a title to before it reaches `Terminal.title` 705 /// handler already truncates a title to before it reaches `Terminal.title`
706 /// (stream_terminal.zig, `max_title_len`), so a title read off the engine 706 /// (terminal/stream_terminal.zig, `max_title_len`), so a title read off
707 /// can never exceed it today. Both ends check it anyway: the peer is not 707 /// the engine can never exceed it today. Both ends check it anyway: the peer is not
708 /// necessarily this version of muxd, and the engine's truncation is not 708 /// necessarily this version of muxd, and the engine's truncation is not
709 /// part of any contract mux is entitled to lean on. 709 /// part of any contract mux is entitled to lean on.
710 pub const term_title_max: usize = 1024; 710 pub const term_title_max: usize = 1024;
src/proxy.zig
Old New
@@ -9,7 +9,7 @@ const TmpDir = @import("testtmp").TmpDir;
9 /// Make a hangup on any of this process's pipes surface as EPIPE from 9 /// Make a hangup on any of this process's pipes surface as EPIPE from
10 /// write() instead of killing it. 10 /// write() instead of killing it.
11 /// 11 ///
12 /// Defence in depth, not a fix: Zig's start.zig already installs a noop 12 /// Defence in depth, not a fix: Zig's std/start.zig already installs a noop
13 /// SIGPIPE handler, so `pump`'s write-error returns are reachable without 13 /// SIGPIPE handler, so `pump`'s write-error returns are reachable without
14 /// this. What it pins is that the reachability belongs to this code instead 14 /// this. What it pins is that the reachability belongs to this code instead
15 /// of to a std default (`std.options.keep_sigpipe`) another module could 15 /// of to a std default (`std.options.keep_sigpipe`) another module could
src/pty.zig
Old New
@@ -137,7 +137,8 @@ pub const Pty = struct {
137 // shell is interactive and therefore ignores the SIGTERM that 137 // shell is interactive and therefore ignores the SIGTERM that
138 // follows; and the blocking waitpid after THAT never returns. One 138 // follows; and the blocking waitpid after THAT never returns. One
139 // leaked fd is a daemon that cannot shut down. Unreachable at one 139 // leaked fd is a daemon that cannot shut down. Unreachable at one
140 // session per daemon, which is why M18 is where it surfaced. 140 // session per daemon, which is why the multi-session daemon is
141 // where it surfaced.
141 // 142 //
142 // Set in the parent rather than asked for at open time because 143 // Set in the parent rather than asked for at open time because
143 // forkpty owns the open: there is no flags argument to pass. Nothing 144 // forkpty owns the open: there is no flags argument to pass. Nothing
@@ -447,7 +448,8 @@ test "Pty: checkExited reports shell exit" {
447 } 448 }
448 449
449 test "Pty: a later spawn does not inherit an earlier session's master" { 450 test "Pty: a later spawn does not inherit an earlier session's master" {
450 // The hangup contract, and the reason M18 needs it stated as a test: 451 // The hangup contract, and the reason a multi-session daemon needs it
452 // stated as a test:
451 // closing a master is how the daemon hangs up on the shell behind it, 453 // closing a master is how the daemon hangs up on the shell behind it,
452 // and that only works if the close is the LAST one. glibc's forkpty 454 // and that only works if the close is the LAST one. glibc's forkpty
453 // hands the master back without CLOEXEC, so before this was fixed every 455 // hands the master back without CLOEXEC, so before this was fixed every
src/replica.zig
Old New
@@ -1,9 +1,9 @@
1 //! The replay core: what a mux client does to mirror a daemon's session. 1 //! The replay core: what a mux client does to mirror a daemon's session.
2 //! Applies snapshot and delta frames to a local engine, tracks the resume 2 //! Applies snapshot and delta frames to a local engine, tracks the resume
3 //! coordinates (`last_seq`/`session_epoch`), and follows the authoritative 3 //! coordinates (`last_seq`/`session_epoch`), and follows the authoritative
4 //! grid. Extracted from client.zig's session() (M-web Task 1) so the CLI 4 //! grid. Extracted from client.zig's session() when the browser client
5 //! client, the wasm core, and the server's test fixtures replay through one 5 //! landed, so the CLI client, the wasm core, and the server's test fixtures
6 //! implementation instead of three. 6 //! replay through one implementation instead of three.
7 //! 7 //!
8 //! Deliberately platform-free: no posix, no fds, no clocks — this module 8 //! Deliberately platform-free: no posix, no fds, no clocks — this module
9 //! must compile for wasm32-freestanding. Anything that needs a terminal, 9 //! must compile for wasm32-freestanding. Anything that needs a terminal,
src/server.zig
Old New
@@ -7929,7 +7929,7 @@ test "Server: a dead name re-attaches as a fresh session with a new epoch" {
7929 } 7929 }
7930 7930
7931 // --------------------------------------------------------------------------- 7931 // ---------------------------------------------------------------------------
7932 // Task 5: every out-of-band instrument learns a session name — dump, the 7932 // Every out-of-band instrument learns a session name — dump, the
7933 // observer's status_req, stats, and the attached-client tail-match rule for 7933 // observer's status_req, stats, and the attached-client tail-match rule for
7934 // status_req/await_req. 7934 // status_req/await_req.
7935 // --------------------------------------------------------------------------- 7935 // ---------------------------------------------------------------------------
src/spawn.zig
Old New
@@ -1,8 +1,9 @@
1 //! Get a daemon onto a socket path: probe, spawn detached, poll until it 1 //! Get a daemon onto a socket path: probe, spawn detached, poll until it
2 //! answers. Three callers: `muxd start`, which is explicit and spelled 2 //! answers. Three callers: `muxd start`, which is explicit and spelled
3 //! out, and the two attach auto-starts (`muxd proxy` and `mux`), which 3 //! out, and the two attach auto-starts (`muxd proxy` and `mux`), which
4 //! share `ensureForAttach` below. The M10 prediction held — auto-start 4 //! share `ensureForAttach` below. The prediction made when the QUIC
5 //! arrived as call sites, not as a rewrite. 5 //! ergonomics landed held — auto-start arrived as call sites, not as a
6 //! rewrite.
6 const std = @import("std"); 7 const std = @import("std");
7 const xdg = @import("xdg"); 8 const xdg = @import("xdg");
8 9
src/wallview.zig
Old New
@@ -1316,8 +1316,8 @@ fn pumpTile(t: *Tile) void {
1316 // Whatever the keyboard left, through everything a plain client's 1316 // Whatever the keyboard left, through everything a plain client's
1317 // keystrokes go through: the mouse split, the wheel, alternate 1317 // keystrokes go through: the mouse split, the wheel, alternate
1318 // scroll, the scrollback view, the prediction and the input frame. 1318 // scroll, the scrollback view, the prediction and the input frame.
1319 // Phase 1 hand-rolled a subset of that, and a zoomed tile had no 1319 // The first version of the wall hand-rolled a subset of that, and
1320 // wheel at all. 1320 // a zoomed tile had no wheel at all.
1321 // 1321 //
1322 // Sent whatever the zoom is doing NOW: these bytes were typed AT 1322 // Sent whatever the zoom is doing NOW: these bytes were typed AT
1323 // this session while this tile was the zoom, and the keyboard is 1323 // this session while this tile was the zoom, and the keyboard is
@@ -1603,8 +1603,8 @@ fn paintDeadZoomLocked(t: *Tile) void {
1603 proto.writeAllFd(t.shared.out_fd, fbs.getWritten()) catch {}; 1603 proto.writeAllFd(t.shared.out_fd, fbs.getWritten()) catch {};
1604 } 1604 }
1605 1605
1606 /// The whole screen an empty wall gets. Phase 1's dead-zoom line, for the 1606 /// The whole screen an empty wall gets. The first wall's dead-zoom line,
1607 /// same reason: a blank terminal with no cursor reads as hung, and the 1607 /// for the same reason: a blank terminal with no cursor reads as hung, and the
1608 /// last `x` is precisely when the user needs to be told that what they see 1608 /// last `x` is precisely when the user needs to be told that what they see
1609 /// is the answer and not a crash. 1609 /// is the answer and not a crash.
1610 fn paintEmptyWallLocked(shared: *Shared) void { 1610 fn paintEmptyWallLocked(shared: *Shared) void {
src/wasm_core.zig
Old New
@@ -1,5 +1,5 @@
1 //! The browser replica core: Engine + Replica + keymap compiled to 1 //! The browser replica core: Engine + Replica + keymap compiled to
2 //! wasm32-freestanding (M-web Task 4). The JS shell is glue; every 2 //! wasm32-freestanding. The JS shell is glue; every
3 //! decision lives on this side of the boundary. 3 //! decision lives on this side of the boundary.
4 //! 4 //!
5 //! FRAME-driven, not byte-driven: the host stages one mux protocol frame 5 //! FRAME-driven, not byte-driven: the host stages one mux protocol frame
src/webhub.zig
Old New
@@ -1,7 +1,7 @@
1 //! The muxweb hub's HTTP/WebSocket layer: route table, 1 //! The muxweb hub's HTTP/WebSocket layer: route table,
2 //! Origin gate, and the WS endpoint naming — the decisions std.http does 2 //! Origin gate, and the WS endpoint naming — the decisions std.http does
3 //! NOT make for us. The connection loop and per-tile pump build on this 3 //! NOT make for us. The connection loop and per-tile pump build on this;
4 //! (Tasks 6-7); the assets are @embedFile'd by webhub_main.zig (the exe 4 //! the assets are @embedFile'd by webhub_main.zig (the exe
5 //! root) and injected here, which keeps this module testable without 5 //! root) and injected here, which keeps this module testable without
6 //! building the wasm artifact. 6 //! building the wasm artifact.
7 //! 7 //!