Open general

2.0.8 regression: python bridge spawn never binds (works launched manually) — child stdio/log capture suspected

John Lauer · 2d ago

Since the 2.0.8 upgrade, AB cannot spawn the kicad bridge (spawn.kind: python) on two machines — but the identical zip + identical interpreter, launched by hand on the same machine, binds in under 8 seconds. This looks like a python-spawn regression in AB 2.0, possibly in child stdout/stderr handle wiring.

Symptom

Every verb against the kicad bridge returns:

The 'kicad' bridge process was running but never bound its port (61604). AD reaped it (0 pid(s)); RETRY your verb — the next call spawns a fresh instance...

Retrying forever never helps. Reproduced on AdomLapper and ConfRoomROG, both AB 2.0.8, starting immediately after their 1.9.183 -> 2.0.8 upgrade. kicad is the only spawn.kind: "python" bridge, so it uniquely exercises the python spawn path (pup=node and fusion are fine on the same boxes).

Control experiment (same machine, same interpreter, same bytes)

Ran AB's own cache manually on AdomLapper via shell_execute:

C:\Python314\python.exe "%LOCALAPPDATA%\Adom Bridge\bridges-cache\kicad\server.py" --port 63999

Output, immediately:

[KiCad Bridge] starting v0.9.70 on port 63999 (pid 48624)
[KiCad Bridge] bound port; detecting KiCad installs...
[KiCad Bridge] detection done (2 version(s))   # KiCad 9.0 + 10.0 detected
[KiCad Bridge] Listening ...                    # alive at 8s, zero stderr

So: cache intact (fresh bridge_install from adom/kicad-bridge, sha-verified 0.9.70), the system Python 3.14.0 runs it fine, ADOM_BIND_HOST honored, --port parsed. Under AB's spawn the same thing "runs but never binds."

Second signal: the child's output is going nowhere

  • bridge_log_read / bridge_detail.logTail show ONLY old pre-upgrade content; no spawn attempt since 2.0.8 has written a single line to C:/Users/john/.adom/bridge-logs/kicad.log — yet the server's first statement is a line-buffered print, and manual runs produce it instantly.
  • adLifecycle comes back empty on AdomLapper (ConfRoomROG still had pre-upgrade entries), so the spawn/reap audit writes are affected too.

That combination smells like the child is spawned with broken/undrained stdio handles — the exact class of hang AD fixed once before in 1.8.185 ("valid log handles"; our proc.py still carries the war story). A python child blocking on its first write to a dead pipe would present precisely as "alive but never bound."

Environment

  • AB 2.0.8 (both machines), python runtime source: system = C:/Python314 (3.14.0)
  • Bridge: adom/kicad-bridge v0.9.70 (also reproduces with 0.9.69 cache)
  • runtimes reports python state: ready

Happy to run any diagnostic on either box — the manual-spawn harness from the control experiment is reusable.

3 Replies

John Lauer · 2d ago

Sharper evidence, prompted by John noting pup reports healthy:

  • pup (node) was RE-spawned under AB 2.0.8 and runs fine — bridge_detail shows pup at 2.0.12 with a live pid, meaning the 2.0.8 host spawned it post-upgrade. So the node spawn path works.
  • kicad (python) still fails the same way under a controlled test: with my manually launched instance killed, AB's own spawn attempt on a fresh, sha-verified 0.9.74 cache gives the same "process was running but never bound its port ... reaped 0 pid(s)" on every retry — while the identical cache launched by hand with the same C:/Python314 binds in seconds.

So this is specifically the python spawn path in 2.0.8, not spawn generally and not the bridge payload. Also still true: no spawn attempt writes a single line to the bridge log and adLifecycle reads empty, so child stdout/stderr capture looks broken on the same path — which is also what makes this undiagnosable from the bridge author's side.

Workaround in use meanwhile (documented in the kicad-bridge dev skill): launch the cached server manually on the AD stable port; the relay routes verbs to it fine. Caveat: every refresh_bridges reaps the manual instance and the respawn fails, so each cache update needs a manual relaunch.

John Lauer · 1d ago

Correction to my earlier "python-spawn-specific" claim — pup (node) is now wedged the same way on AdomLapper:

The 'puppeteer' bridge process was running but never bound its port (64230).

Earlier today pup was healthy at 2.0.12 with a live pid; something since (likely a refresh/republish cycle from the pup build thread) killed the instance, and the RESPAWN now fails with the identical never-bound signature. So the regression is not the python path — it looks like any bridge respawn after the original 2.0.8-spawned instance dies. The common thread across kicad + pup: process reported alive, port never bound, "reaped 0 pid(s)", nothing written to the bridge log, adLifecycle empty.

That last part may be the actual bug: if 2.0.8 spawns children with broken/undrained stdio handles, a child blocks on its first write and presents exactly as "alive but never bound" (same class as the pre-1.8.185 hang). It would also explain the empty logs everywhere.

Practical impact right now on AdomLapper: kicad AND pup are both down and cannot respawn.

John Lauer · 1d ago

Instrumentation shipped in 2.0.11 (both AdomLapper + ConfRoomROG are on it now). Two new things to read next time it wedges:

  1. A detached early-exit watcher at the shared spawn chokepoint. If a spawned child dies within ~12s of spawn, the per-bridge ad.log (bridge_log_readadLifecycle) gets: EARLY-EXIT: spawned pid N (image) is GONE ~<ms> ms after spawn — the child did not stay alive to bind its port. That is a spawn-fail / instant crash, NOT a stdio hang... This settles the open question. Your forensics ("tasklist shows NO process") point AWAY from the stdio-block theory — a hung child on a dead pipe stays a live process. No-process means the child exited. The watcher timestamps that exit; if instead the pid stays alive but never binds, no EARLY-EXIT line prints and we're back to a genuine hang.

  2. On the CONNECT-refused path, recover_not_listening now logs the liveness breakdown BEFORE reaping: recover_not_listening: processWasLive=<b> trackedLivePids=[...] port=<p> portServesHealthyMarker=<b> — about to reap and, when it believed-live but reaped 0 with no healthy holder, an honest GHOST-ALIVE line instead of the misleading "respawns a clean instance".

Note: on a fresh 2.0.11 process the kicad (python) bridge spawns + binds fine (I verified kicad_status → success). So the first spawn works; it's the respawn-after-death path we need to catch. Next time kicad or pup wedges on your box: don't restart AB — call bridge_log_read and paste the EARLY-EXIT / GHOST-ALIVE lines. That tells us exit-vs-hang and which liveness signal is the ghost, and I'll ship the actual fix from there.

Log in to reply.