a73x

145bed7e

docs: the cold attach is one ssh run

a73x   2026-08-29 14:38

Commit message
docs: the cold attach is one ssh run

CLAUDE.md's "a daemon starts only when someone asks" invariant: the ask
is a WORD now, and the rule holds by argv rather than by a client branch.
"Remote is still ssh" names the new remote command and what a ≤15 box
answers it with.

README's remote section: one ssh login, not three.

decisions.md: one dated entry — the three runs and where they were
measured, why the exit-1-vs-255 reap existed and why the fold deletes the
question it answered, why the remote can be trusted with the decision
(`mux d start` was already idempotent), and mosh-server's shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wi2HnuF1EK8HgViU11YLV

CLAUDE.md
Old New
@@ -185,15 +185,19 @@ paths (`src/cli/main.zig` is the daemon). Test fixtures in `test/`:
185 per machine is the accepted cost. `mux d run` exits 0 on every shutdown — 185 per machine is the accepted cost. `mux d run` exits 0 on every shutdown —
186 a supervisor reads nonzero on a clean stop as a crash — and nonzero only 186 a supervisor reads nonzero on a clean stop as a crash — and nonzero only
187 for the boot failures `main.run` picks before anything binds. 187 for the boot failures `main.run` picks before anything binds.
188 - **A daemon starts only when someone asks; a read never starts one.** 188 - **A daemon starts only when someone asks; a read never starts one, and
189 `mux d start`, or the local client's own entry spawn, are the only 189 the ask is a WORD.** `mux d start`, `mux d endpoint --start`, or the local
190 starters. `mux d endpoint` and `mux d proxy` answer what is on the box and 190 client's own entry spawn are the only starters. Bare `mux d endpoint` and
191 exit 1 when nothing is — the wall polls `endpoint` over ssh once a second 191 `mux d proxy` answer what is on the box and exit 1 when nothing is — the
192 per host, and a poll that started daemons undid every remote `mux d stop` 192 wall polls `endpoint` over ssh once a second per host, and a poll that
193 within a second (found on a live box). The dial a user ASKED for — the 193 started daemons undid every remote `mux d stop` within a second (found on
194 `mux HOST` entry dial, picker Enter — runs `ssh HOST 'mux d start'` once 194 a live box). The dial a user ASKED for — the `mux HOST` entry dial, picker
195 and redials once (`HandoffTarget.asked`, spent after the first attempt); 195 Enter — runs `ssh HOST 'mux d endpoint --start'` ONCE, and that run's
196 the poll, `mux hosts` and every redial read `unreachable` instead. 196 announce IS the redial: the remote ensures the daemon and announces on the
197 same stdout (`HandoffTarget.asked`, spent after the first attempt). The
198 poll, `mux hosts` and every redial spell the bare verb and read
199 `unreachable` instead, so the rule holds by argv rather than by a client
200 branch. `--start` is `endpoint`'s alone; every other verb refuses it.
197 `asked` DEFAULTS to false: a new dial path that forgets the field starts 201 `asked` DEFAULTS to false: a new dial path that forgets the field starts
198 nothing, because a missing start says so on screen while a spurious one is 202 nothing, because a missing start says so on screen while a spurious one is
199 a daemon on someone else's box that nothing reports. 203 a daemon on someone else's box that nothing reports.
@@ -213,11 +217,14 @@ paths (`src/cli/main.zig` is the daemon). Test fixtures in `test/`:
213 on the parent and hits `unreachable // own pid is always valid` 217 on the parent and hits `unreachable // own pid is always valid`
214 (decisions.md, 2026-08-28). 218 (decisions.md, 2026-08-28).
215 - **Remote is still ssh, and still by name.** `handoff` spells 219 - **Remote is still ssh, and still by name.** `handoff` spells
216 `ssh HOST 'mux d endpoint'` / `'mux d start'`, so a remote running an 220 `ssh HOST 'mux d endpoint'` / `'mux d endpoint --start'`, so a remote
217 installation of v0.0.1-15 or older reads `[unreachable]` until it is 221 running an installation of v0.0.1-15 or older reads `[unreachable]` until
218 upgraded. There is no in-place upgrade across the rename either: a ≤15 222 it is upgraded — ≤15 knows neither the mode word nor the flag, and answers
219 daemon probes the candidate for `muxd <version>` and this binary answers 223 a usage page on stderr with rc 2 and no announce (measured against the
220 `mux <version>`, so that one migration is `mux d stop` then `mux d start`. 224 released v0.0.1-15 tarball). There is no in-place upgrade across the
225 rename either: a ≤15 daemon probes the candidate for `muxd <version>` and
226 this binary answers `mux <version>`, so that one migration is
227 `mux d stop` then `mux d start`.
221 - **The layout sidecar is derived convenience, not authored intent.** 228 - **The layout sidecar is derived convenience, not authored intent.**
222 `$XDG_STATE_HOME/mux/layout` stores the pane tree on last detach, from 229 `$XDG_STATE_HOME/mux/layout` stores the pane tree on last detach, from
223 every wall left on a TERMINAL (`shared.is_tty`) — a piped `mux` is a wall 230 every wall left on a TERMINAL (`shared.is_tty`) — a piped `mux` is a wall
README.md
Old New
@@ -189,14 +189,15 @@ mux HOST # attach; Ctrl-\ d detaches, running it again reattaches
189 `mux d start` spawns the daemon detached and waits until it answers, so it 189 `mux d start` spawns the daemon detached and waits until it answers, so it
190 is safe to run every time — if one is already up it says so and exits 0. 190 is safe to run every time — if one is already up it says so and exits 0.
191 191
192 `mux HOST` runs `mux d endpoint` on HOST over ssh (finding a `mux` in 192 `mux HOST` runs `mux d endpoint --start` on HOST over ssh, ONE run
193 `~/.local/bin` even though ssh's non-login shell leaves it off PATH). On a 193 (finding a `mux` in `~/.local/bin` even though ssh's non-login shell leaves
194 box with no daemon that verb answers nothing, and `mux HOST` runs `mux d 194 it off PATH). That verb ensures a daemon and then announces the
195 start` there once, over the same ssh, before asking again — so the 195 coordinates on the same stdout, so a cold attach costs one ssh login and
196 `ssh HOST 'mux d start'` line above is optional after the first time. 196 the `ssh HOST 'mux d start'` line above is optional after the first time.
197 Nothing else starts a daemon: the wall's per-host poll, `mux hosts`, every 197 Nothing else starts a daemon, because nothing else spells the flag: the
198 reconnect and `mux --via 'ssh HOST mux d proxy'` all report the box 198 wall's per-host poll, `mux hosts`, every reconnect and `mux --via 'ssh HOST
199 unreachable and leave it alone. Your 199 mux d proxy'` run the bare `mux d endpoint`, report the box unreachable and
200 leave it alone. Your
200 ssh config (aliases, ports, 201 ssh config (aliases, ports,
201 ProxyJump) all keeps working, since `mux` never parses HOST. That ssh 202 ProxyJump) all keeps working, since `mux` never parses HOST. That ssh
202 fetches the daemon's QUIC port and key once and the session moves onto 203 fetches the daemon's QUIC port and key once and the session moves onto
docs/decisions.md
Old New
@@ -7504,3 +7504,24 @@ Found by the rewritten e2e leg: a `POST` carrying no body and no
7504 `content-length` — what `curl -X POST` sends — reached std's `discardBody` 7504 `content-length` — what `curl -X POST` sends — reached std's `discardBody`
7505 assert and ABORTED the hub, every tile with it. Any answer to a 7505 assert and ABORTED the hub, every tile with it. Any answer to a
7506 body-capable request that declared no length now closes its connection. 7506 body-capable request that declared no length now closes its connection.
7507
7508 ## 2026-08-29 — the cold handoff is one ssh run: `mux d endpoint --start`
7509
7510 - **The three runs.** A cold `mux HOST` cost three ssh logins because the
7511 CLIENT decided to start: `mux d endpoint` exited 1 having written nothing,
7512 the client reaped the child to read that code, ran `ssh HOST 'mux d start'`,
7513 then ran `mux d endpoint` again. Measured on the suite's ssh shim, one line
7514 per run: `e2e_04_handoff.sh`'s cold leg pinned 3 and now pins 1.
7515 - **Why it folds.** The reap existed only to tell the remote's refusal (exit
7516 1) from ssh's own failure (255), which the announce cannot; the remote
7517 deciding deletes the question, and `mux d start` was already idempotent, so
7518 "ensure, then answer" has no race in it. `mosh-server new`'s shape: one ssh
7519 run whose first stdout line is the coordinates.
7520 - **"A read never starts a daemon" now holds by ARGV**, not by a branch a new
7521 dial path could forget: a poll, `mux hosts` and every redial spell
7522 `mux d endpoint` and start nothing. `--start` is `endpoint`'s alone
7523 (`mux d stats --start` is rc 2, not a flag that vanishes) and its ensure
7524 forwards `--sock`, so the daemon binds the path the announce then probes.
7525 - **What ≤15 remotes read.** Measured against the released v0.0.1-15 tarball:
7526 rc 2, a usage page on stderr, nothing on stdout — so the asked dial reads
7527 `[unreachable]`, the same answer the rename already gave them.