fix(audio): #138 — reprobe escalates to a graph restart, never opens with one
All checks were successful
Check / eval (push) Successful in 3m53s

Bernardo's Meet calls lost their microphone because our own dock-audio
reprobe restarted PipeWire/Pulse/WirePlumber on every monitoradded (#100).
Chromium's audio service does not reconnect when the server disappears
under it, so it kept running and enumerated nothing; Zoom, started fresh
into a settled graph, looked fine and made it read like a browser bug.

reprobe now escalates cheapest-first — select → repair a parked card's
profile → restart the graph — so an ordinary plug never touches the graph
and no client loses its connection. wait_for_dock_sink polls for the sink
the plug is about to publish, so "not yet" is not mistaken for "not ever"
and cannot fall through to the restart. The restart stays as rung 3 for
the codec that only publishes its route after a re-probe: the one claim
the original comment made, and the only one it never demonstrated.

Verified on the dev box against the live graph: rung 1 selects the same
sink the restart-first path picked (HiFi__HDMI1__sink) in 2.0s instead of
~3.5s with pipewire/pulse/wireplumber MainPIDs unchanged; rung 3 reachable
and restarts+retries. Diagnosis proof was ordering across four boots —
restart-after-Chromium in both broken sessions, 24s before it in the
healthy one — and Bernardo reproduced on demand with a dock/undock.

Sweep: BACKLOG #138 deleted, ROADMAP gets the design record (incl. the
stale restart-first sentence in the #100 entry), V3 queued on the dev box
(relogin first — the watcher is exec-once), journal entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 09:43:14 +01:00
parent 44aac0fcde
commit 9e64358225
5 changed files with 113 additions and 61 deletions

View File

@@ -211,57 +211,6 @@ scaled splash — the LUKS prompt especially, since a passphrase box half off
the panel is the failure that matters. Pass = docked boot + shutdown centred
on both heads; undocked unchanged.
### 138. Our dock-audio reprobe restarts PipeWire under running apps (Chromium loses its devices)
Bernardo 2026-07-16, refined by his own hypothesis (pipewire restarts →
Chromium never reconnects) — which the journal confirms, and names **our own
code as the thing doing the restarting**. Symptom: Google Meet reports no mic
or speakers in native Chromium; Zoom is unaffected; it did **not** reproduce on
a fresh boot, only after days of uptime + a hibernate.
**Mechanism.** `modules/home/dock-audio.nix:153` — on every `monitoradded`, the
`reprobe` path runs `systemctl --user restart pipewire.service
pipewire-pulse.service wireplumber.service`. A full graph restart, on every
physical monitor plug (#100). Chromium's audio service does not re-establish
its PulseAudio connection when the server disappears under it, so it keeps
running with a dead one and enumerates nothing — hence Meet's wording, hence
"restarting Chromium fixes it". Zoom survives because it is a Flatpak that
reconnects, and because it is generally started fresh into an already-restarted
graph. **Hibernate is not the cause** — it is only how a Chromium survives long
enough to still be running when a plug event arrives; resume-while-docked then
generates the `monitoradded` that fires the reprobe.
**Evidence — ordering is the whole tell** (`Started app-org.chromium.Chromium-*.scope`
vs `reprobe-start`, four boots, no exceptions):
| Session | Chromium | Graph restart | Order | Meet |
|---|---|---|---|---|
| boot -3 (Jul 14→15) | 14:06:49 | 14:07:52 (+2 more; then 10:52 next day) | **after** | broken |
| boot -2 (Jul 15 daytime) | 11:10:34 | 11:31:22 | **after**, +21 min | broken |
| boot 0 (Jul 16, fresh) | 08:04:19 | 08:03:55 | **before**, 24 s | works |
Today is the negative control, and it is luck, not a fix: the restart missed
Chromium by 24 seconds. **Prediction that confirms this outright — dock/undock
now, then reload Meet: it should break in the current session**, with no reboot
and no hibernate. That is the cheap V3 for the diagnosis, and it is also the
regression test.
**What to fix.** Not Chromium — the graph restart is a sledgehammer we
inherited: the header comment justifies it only as "the recovery used by the
old working flake", i.e. it was never shown to be *needed*, while its collateral
(every long-lived audio client dropped) was never priced. It is also not rare —
boot -3 took three restarts in 82 seconds (14:07:52 / 14:08:58 / 14:09:09;
the `mkdir` debounce only collapses events within one run, not a plug storm),
and each one drops audio for every app on the machine. Shape: **try selection
first, restart only if nothing routable appears** — `select_first_dock` already
falls back to `repair_dock_cards`, which fixes the parked-card case
(`set-card-profile`) *without* touching the graph, so the restart may have no
remaining job at all. If some HDMI codec really does need it (the other claim
in that comment), prove it on this hardware and scope the restart to that case.
Pass = plug/unplug the dock with Chromium open → output still follows to the
monitor **and** Meet still sees its devices; `journalctl -t nomarchy-dock-audio`
shows no graph restart on the ordinary path.
### 139. Terminal floats open full-screen — % size rules are ignored, and Kitty remembers
Bernardo 2026-07-16: calcurse and nomarchy-doctor float but fill the screen —

View File

@@ -509,6 +509,19 @@ Everything else below stays open; order is convenience, not a gate.
active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU
PyTorch/Ollama smoke) and the XDNA NPU driver loading.
- [ ] **Fingerprint**`fprintd-enroll` + (opt-in PAM) login/sudo.
- [ ] **#138 dock audio does not break a running browser** — the plug event
itself is the only thing V2 could not do (the tool was driven by hand;
Hyprland calls the same entry point). **Relogin first** — the watcher is
`exec-once`, so `nomarchy-home` alone leaves the old process running and
you would be testing nothing (round 6's lesson). Then, with Chromium
**already open** on a Meet call: dock/undock. Pass = output still follows
to the monitor, **Meet keeps its mic and speakers without restarting the
browser**, and `journalctl --user -t nomarchy-dock-audio` shows
`selected=…` with **no** `action=graph-restart-fallback`, while
`systemctl --user show -p MainPID --value pipewire.service` is unchanged
across the plug. If a `graph-restart-fallback` line does appear, rung 3
fired on a healthy plug — that is a bug, not the recovery working: file
it with the surrounding journal.
- [ ] **System ▸ Firmware menu on real LVFS hardware** (item #43,
`nomarchy-menu firmware`) — on a machine whose firmware/SSD/dock is
on LVFS: open Menu ▸ System ▸ **Firmware**; confirm the terminal runs

View File

@@ -19,6 +19,38 @@ Template:
---
## 2026-07-16 — #138 dock audio: the graph restart was the bug (44aac0f + this)
- **Task:** #138 — Meet "no mic or speakers" in Chromium; Zoom fine; no repro
on a fresh boot. Bernardo's hypothesis (pipewire restarts → Chromium never
reconnects) was right; the restarter turned out to be **us**.
- **Did:** `dock-audio.nix` `reprobe` restarted PipeWire/Pulse/WirePlumber on
every `monitoradded` (#100). Now escalates cheapest-first: select → repair a
parked card → restart. Added `wait_for_dock_sink` so a slow sink is not read
as "nothing to select" and cannot fall through to the restart. Filed #137/
#139/#140/#141 from the same T14s round in 44aac0f.
- **Verified:** V0 `nix flake check --no-build`. **V2/V3 on the dev box** (real
hardware, real graph): built the HM activation package with
`--override-input nomarchy path:…` and ran the real tool — rung 1 selects the
*same* sink the restart-first path picked (`selected=…HiFi__HDMI1__sink`) in
2.0s vs ~3.5s, with pipewire/pulse/wireplumber MainPIDs **unchanged**; rung 3
is reachable and restarts+retries as designed. Diagnosis proof: `Started
app-org.chromium.Chromium-*.scope` vs `reprobe-start` across 4 boots —
restart-after-Chromium in both broken sessions, 24s *before* in the healthy
one; Bernardo then reproduced on demand with a dock/undock (09:35:30).
- **Pending:** V3 queued (dev box): real plug with Chromium open on Meet —
**relogin first**, the watcher is `exec-once` (round 6's lesson). Bernardo
must `nomarchy-home` to pick this up; his Chromium needs one restart to
regain audio (his own 09:35 test killed it, not the fix).
- **Process note (mine, worth not repeating):** the rung-3 test was meant to be
safe via a stubbed `systemctl`, but the `sed` anchored on leading whitespace
that **Nix's indented strings strip**, so the stub never applied and the run
restarted his live graph for real. It came back clean (services active, dock
sink still default) and cost nothing extra only because Chromium was already
broken. Lesson: when a test's safety depends on a substitution, **assert the
substitution landed** before running the thing it protects you from.
- **Next suggestion:** #139 (terminal floats — two faults, both root-caused and
cheap) or #141 (4 jsonc lines, `$TERMINAL` absent from Waybar's env).
## 2026-07-15 — #107 upgrade path proven; state-read tip fixed
- **Task:** Bernardo feared rebuilding on the state.json rename ("what if it
doesn't boot"). Verify the real upgrade path from a legacy checkout.