Commit 43a44247 by PLN (Algolia)

docs(log): 040 — the phantom port, and what name-based identity cost us

Captain's log for the gear-failure pass: one zombie lcxl3-driver holding a
stale binding to an unplugged mk3 explained three of PLN's five symptoms,
the fourth was a mislabeled tray button, and the fifth was tooling that
had been running invisibly all along.

Written for mining later — the sharp bit is that name-based identity is
the actual bug, and it caught the investigation as well as the rig: a
subagent grepped aconnect for LCXL3, hit the driver's own phantom virtual
port, and reported the hardware was back.
parent 57dc2d91
---
log: 040
title: "The phantom port one zombie driver, four symptoms"
date: 2026-09-05
task: "n/a (PLN gear-failure report mid-session)"
tags: [rig, lcxl, midi, systemd, kwin, perf, forensics]
shareable: true
---
## Cap (what & why)
PLN reported five gear problems at once before a play session: "launch gig"
started Pulsar but not Ardour; a freshly-plugged classic LCXL was not
autoconnected to Midi Through; the board showed only its factory colours; no
event logging visible; no aseqdump or MIDI monitor anywhere. Five complaints,
and the working hypothesis of "five bugs" was wrong.
## Manœuvre (how)
Ran the diagnosis off runtime ground truth first (`aconnect -l`, `lsusb`,
process lineage, `journalctl`, the Bridge's own `/api/rig`) and only then read
code, with four subagents reading in parallel. The runtime evidence repeatedly
overturned the code reading — twice an agent's confident conclusion was wrong
because it trusted a name.
Then fixed the four that were safe, held the one that wasn't.
## Prise (findings / artifacts)
- **One root cause behind three symptoms.** `lcxl3-driver.service` started at
14:36 bound to a real mk3; the mk3 was then swapped for the classic and the
driver was never restarted. It kept writing mk3 SysEx into a dead port
(`mido.send()` on a vanished ALSA destination does not raise) and kept
publishing its virtual port **`ParVagues LCXL3`**.
- That phantom port poisoned `midi-autoconnect.sh`, whose candidate list is
first-match-wins: `("ParVagues LCXL3" "LCXL3" "Launch Control XL")`. It
latched onto the ghost at 13:22 and never noticed real hardware again.
- And it kept the correct painter *off*: `lcxl3-driver` declares
`Conflicts=lcxl-leds-watch`, so the unit that speaks the classic's protocol
(`00 20 29 02 11`, template/palette) had been dead since 11:44 while the
driver spoke mk3 (`00 20 29 02 15`, RGB, 34 targets) at nothing.
- **"Launch Gig" was never a gig launcher.** `perf-tray.py:318` was
`_launch("pulsar")` — editor-only *by design* (#116). The real launcher is the
Bridge's RIG UP, which `/api/rig` showed had never run once in that Bridge
process's 6.7 days (`converge: started 0.0, rc null, out ""`).
- **183 Pulsar SIGABRTs** since 2026-08-19, 80 in one 70-minute window.
`perf.sh:630` launched Pulsar in `$(dirname "${BASH_SOURCE[0]}")`, but perf.sh
is deployed root-owned as `/usr/local/sbin/perf-audio`, so Pulsar was handed
`/usr/local/sbin` as its project directory. The Bridge's perf watcher calls it
every ~30s and every mode flag routes through the same function.
- **Logging existed all along.** `gig-log.service` writes
`~/.local/share/parvagues/gig-log/gig-<stamp>.jsonl` — CC/PB coalesced per
second, every note kept. The two invisible `aseqdump -p 20:0` belonged to
gig-log and the Pulsar HUD. `midiviz.py` is the visual lens, and RIG UP already
lists it in `POST_APPS` — the monitor PLN wanted was wired to the button he
had never pressed.
- **A live double-CC path.** SC's boot runs `MIDIIn.connectAll` and
`MIDIFunc.cc` accepts `src` and never checks it, so after every SC restart the
surface fed SC twice (raw + via Midi Through): every CC handled twice, every
button `noteOn` double-firing into `~lcxlChordCheck`. A double noteOn on a
toggle is a no-op — that is how a mute button silently stops working.
Shipped: `361a05b` (autoconnect rule 3), `d1dbae3` (midiviz: app id, KWin rule,
CC numbers, unit), `c7ac174` (perf.sh DWIM removal), plus the rig_units painter
policy fix. PLN's play-test after the repair: *"just played rose_rouge works
great! and the midi mon is beautiful!"*
## Sel (the shareable learning)
- **A stale binding is worse than a crash.** A crashed driver is visible; a
driver holding handles to hardware that left is green in every check, and its
virtual port actively lies to everything that identifies gear by name.
- **The phantom fooled the investigation too.** A subagent grepped `aconnect -l`
for "LCXL3", hit `ParVagues LCXL3`, and concluded the mk3 was plugged back in.
Same trap as the autoconnect. The lesson isn't "agents are unreliable" — it's
that *name-based identity is the bug*, and it catches whoever uses it.
- **Verify identity, never infer it.** midiviz's Wayland app id was assumed to be
`midiviz` (Qt's documented `applicationName` fallback). Queried live, it was
**`python3`** — so a window rule would have matched every Python GUI on the box,
and `focus_window("midimon")` could never match anything. One D-Bus query
changed the design.
- **Five green checks, one wrong button.** Nothing about the gig launch was
broken. The label promised a gig and delivered an editor, and 86 seconds of
waiting did the rest. Naming is a correctness property.
- **A ✓ in a log is not evidence.** perf.sh verified its launch with a 0.5s
`pgrep` presence poll, which caught the doomed Electron process alive during
startup and printed "✓ Pulsar launched" before it aborted — 183 times.
- **Respect the invariant you're about to "fix".** The driver's
`StartLimitIntervalSec=0` looked like the crash-loop bug. Its comment
explained it: the USB-C hub drops the surface, and bounded retries leave it
"deaf for the rest of the session". The trigger was the boot policy, not the
retry count.
## Hameçon (hook)
"Five things broke before the gig. Four of them were the same ghost — a MIDI
port belonging to a controller that had already left the building, and it lied
to every tool that asked for gear by name. Including me."
## Sillage (what it unlocks)
- The generation rule is now stated: **neither painter may be auto-started
blind** — only the plugged hardware can answer "which painter", and
`gig-up.sh leds()` owns it.
- midiviz is a first-class instrument: permanent, on every desktop, and it now
prints the control you just touched as `^53=127` — the literal token to type
into a pattern. Wiring stops requiring a second window.
- The lean-UI direction got named and matched to PLN's own board item ("The
Bridge as the single ParVagues cockpit"): **one reconciler, three skins; no
modals in the hot path; the performing session is never the archive.**
- Next: the Ardour double-launch defect this session exposed the hard way (see
`armada/tasks/041-*` / RESUME), then `gig-down`.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment