# PR #23571 evidence: dev supervisor stops when its parent dies

Trees (from `git rev-parse HEAD`, recorded at the top of every transcript): main `8d3b061121fe65669d5d43d011b48cd3027a4108` (~/wt/main-8d3b061), head `85ebfe32d28a21b25d3ec6540fe58dadf1c01fbc` (~/wt/pr-23571). Host `/bin/sh` -> `/usr/bin/dash`. All T3CODE_* vars were unset; each run used its own `--home-dir /tmp/ev-23571/home-<tree>-<run> --no-seed` under `setsid headless-desktop`. Main ran first, then head, never both at once. Every run's session was empty at teardown (`remaining in SID: 0`).

"respawns" = SIGCHLD with `CLD_EXITED si_status=1` that the supervisor received, counted from its strace. That count is the orphaned supervisor relaunching Electron in a crash loop.

| row | main result | head result | PASS/FAIL vs matrix | evidence |
|---|---|---|---|---|
| 0 baseline | VPPACK(pack-bin --watch) -> `/bin/sh -c node scripts/dev-electron.mjs` -> SUP -> electron; SUP PPid = sh | VPPACK -> SUP directly (P1 == VPPACK, no sh) -> electron | PASS (main as expected, head as expected) | `*-r1.transcript.txt`, `*-r3.transcript.txt` (pids-start.txt with the full ancestry up to ROOT, `r0.tree.txt`) |
| 1 vp pack SIGKILL | SUP stays alive for +6 s; sh reparented to subreaper 2928; no exit_group; 22 Electron respawns in 11 s; after `touch` a new Electron (1710931) appears under SUP; no `before-quit` after t0. **Bug reproduced.** SUP had to be killed at teardown (exit 143). | `kill(ELEC,SIGTERM)` at t0+0.16 s; `before-quit received` 11:54:02.060; `exit_group(129)` at t0+1.52 s; SUP gone by the +2 s snapshot; nothing left in SID; no Electron after touch | PASS | `main-r1.*`, `head-r1.*` |
| 1b row 1 after rebuild | SUP2 survives orphaned, 22 respawns, new Electron after touch | SUP2 `kill(ELEC)` then `exit_group(129)` at t0+1.55 s; `before-quit` 11:56:06.600; gone by +2 s | PASS (fresh parentPid per supervisor) | `*-r3.transcript.txt`, `*-r3-r1b.strace.txt` |
| 2a SIGINT to pgrp | `exit_group(130)` at t0+1.43 s; tree empty by +2 s | `exit_group(130)` at t0+1.43 s; tree empty by +2 s | PASS (parity). The `before-quit received` count after t0 is 0 on **both** trees; Electron got the group SIGINT directly and the line never reached run.log. VPPACK, SUP and ELEC were all in the same PGID. | `*-r2a.*` |
| 2b SIGTERM to vp pack only | vp pack does **not** forward: SUP survives orphaned for +6 s with no exit_group; killed at teardown (143) | `kill(ELEC)` then `exit_group(129)` at t0+1.50 s; `before-quit` 11:59:43.075; gone by +2 s | PASS: **improvement** (matrix's second case) | `*-r2b.*` |
| 2c SIGTERM to session | `exit_group(143)` at t0+1.37 s; empty by +2 s | `exit_group(143)` at t0+1.35 s; empty by +2 s | PASS (parity) | `*-r2c.*` |
| 3 rebuild replaces supervisor | old SUP `exit_group(143)`; SUP2 parent = **new `/bin/sh -c`**; VPPACK unchanged; 1 supervisor, 1 Electron main | old SUP `exit_group(143)`; SUP2 **PPid == VPPACK** (vite-plus node); VPPACK unchanged; 1 supervisor, 1 Electron main | PASS | `*-r3.transcript.txt`, `*-r3-r3.strace.txt` |
| 4a dash | `readlink -f /bin/sh` = `/usr/bin/dash` (in every transcript header) | same | PASS | transcripts |
| 4b /bin/sh = bash (private mount ns) | inside the ns `sh` = bash 5.2.21; no `sh` even on main (VPPACK -> SUP). Row 1: SUP survives, 22 respawns, new Electron after touch (bug). Row 3: SUP2 is a direct child of VPPACK, 1 supervisor. 1b: SUP survives, 24 respawns. | Row 1: `kill(ELEC)` then `exit_group(129)` at t0+1.74 s, gone by +2 s. Row 3: SUP2 is a direct child. 1b: `exit_group(129)` at t0+2.1 s, gone by +3 s. | PASS | `*-r4-r1.*`, `*-r4-r3.*` (each transcript has `r4-shcheck.txt` and `outside-check.txt`) |
| 4c macOS / Windows | not verified | not verified | — | — |
| 5 unref | (a) no match | (a) `285:  }, parentWatchIntervalMs).unref();` (b) unref snippet exits 0 in 0.040 s; control without unref exits 124 under `timeout 3` (3.006 s). (c) exit timing after signals matches main within 0.02 s (2a 1.43/1.43 s, 2c 1.37/1.35 s) | PASS | `r5-r6.txt` |
| 6 unit test | the new test is absent (grep count 0) | 8 passed, including `dev supervisor stops Electron and exits once its parent process goes away`; 9984f378a3 touches only the test file | PASS | `r5-r6.txt` |
| 7 kill grandparent (cross-env vp node) | VPPACK and SUP survive; 16 respawns | VPPACK and SUP survive; 16 respawns | as expected (same as main; known scope limit) | `*-r7.*` |

## Notes and deviations

- **Electron dies on its own on both trees.** In rows 1, 1b, 2b and 7, the original Electron is gone by +1 s on both trees. Once vp pack dies, dev-runner exits (`DevRunnerProcessExitError ... code 1`), the web dev server goes down with it, and Electron exits with status 1. The bug on main is therefore an orphaned supervisor that keeps relaunching Electron (16–24 respawns in about 10 s), not one long-lived Electron. On head the supervisor still sends `kill(<ELEC>, SIGTERM)` first (rows 1, 1b, 2b, 4b-r1), then exits 129.
- **4b caveat:** the transcript header inside the namespace still prints `/bin/sh=/usr/bin/dash`, because `readlink -f` resolves the path, and that path is bind-mounted with bash. `r4-shcheck.txt` (`sh=5.2.21(1)-release`) is the proof. Outside the namespace, before and after: `/bin/sh -> /usr/bin/dash`, empty `BASH_VERSION`, 0 dash mount entries in the host mount namespace.
- **4b missing marker:** in head 4b row 1 and row 1b, `before-quit received` does not appear in run.log after t0, though strace shows `kill(ELEC, SIGTERM)` (row 1) and exit 129. The Electron had already crashed or was replaced at that point, so the marker is missing; the supervisor exit is still proven by strace.
- `/usr/bin/time` is not installed, so row 5b used the bash `time` builtin.
- Exit timings come from strace `exit_group` versus the `*.t0` file (kill issued right after t0 is written).
