Closed general

Dashboard tab dies after sleep: ab comes back on a different local port and the page is a refused connection

John Lauer · 1mo ago ·closed by John Lauer

The KiCad dashboard page ab serves on 127.0.0.1 becomes a dead link after the machine sleeps, and the user is left looking at "127.0.0.1 refused to connect" with no way to tell that anything still works.

Report (adom/kicad-bridge#98, kaleymer): opened the KiCad dashboard, machine slept, woke up, and the tab shows the browser's refused-connection page.

  • URL in the tab: http://127.0.0.1:8931/
  • Machine: k-zephyrus, an ROG Zephyrus G16
  • Everything else was fine: the KiCad Bridge verbs were unaffected, this is only the page.

What I told the user, which is the workaround, not the fix: open the dashboard from the tray icon or the Dock Bar card instead of reloading the tab, because that opens whatever port ab is on now.

Two asks.

  1. Reclaim the previous port across a resume and across an ab restart. Persist the last bound port and try it first, falling back to a new one only when it is genuinely taken. A dashboard tab, a bookmark or a pinned window then keeps working across a sleep, which is the normal way people leave these pages open.
  2. Make a tab that outlives its port recoverable. Once the port is dead the page cannot help itself, so the recovery has to be outside it: a stable entry point that redirects to the live port, or a tray action that finds the open dashboard tab and navigates it, so the user never has to know a port number exists.

Filed by the KiCad Bridge owner because the dashboard server is ab's, not the bridge's. Happy to test either fix on a KiCad dashboard once it is in an insiders build.

3 Replies

John Lauer · 1mo ago

Ack. The dashboard server binds an ephemeral loopback port at boot (the PORTS.md rule: never a fixed port on Windows, Hyper-V reserves ranges), so a sleep/resume or an ab restart moves it. Plan: persist the last bound port and try it first (fall back only if it is genuinely taken), and give the page a stable entry point that redirects to the live port so a bookmark or pinned tab keeps working. Not in the 2.1.131 build that is going out now; next one. Will update here.

John Lauer · 1mo ago

Shipped in insiders 2.1.133.

Why the tab died. The KiCad bridge's manifest pins no port (spawn.port: 0), so 8931 on k-zephyrus was an ab-allocated ephemeral port. ab already persists a bridge's port (~/.adom/bridge-ports.json) and reclaims it on every spawn, but an allocated port can legitimately move: Windows (Hyper-V / WinNAT / WSL2) reserves port blocks at boot and the blocks change, so a port that was fine before can be inside an exclusion range afterwards (ab re-picks rather than wedge the bridge, 2.0.62), and a bridge that fails to spawn repeatedly has its port cleared. A tab a human left open on the old port has no way to learn the new one, and the per-run log on k-zephyrus was overwritten before anyone could read which of those happened.

What changed

  1. A stable address, on ab's fixed port: GET http://127.0.0.1:24680/open/<bridge> (optional ?path=/sub/page, absolute paths only) resolves the bridge's live port at click time, starts a stopped dynamic bridge if needed, and redirects (307). Unknown bridge answers 404 {errorCode:"unknown_bridge"}; a bridge that cannot be started answers a plain page that retries every 5 s instead of the browser's "refused to connect". ab's port is installer-reserved (24680..24689), so this URL survives sleeps, restarts and port moves.
  2. bridge_list rows carry it as stableUrl. Put that on the Dock Bar card, in the tray/bookmark, and in any "open the dashboard" link; never the bridge port URL.
  3. Every port move is logged as a PORT-MOVE: bridge '<name>' ... line in %LOCALAPPDATA%\Adom Bridge\startup.log (which persists across runs), with the reason (reserved range, or cleared after failures), so the next report can be diagnosed after the fact.

Proof (arav-rog on 2.1.133, from inside the box):

GET /open/puppeteer -> 307 Location: http://127.0.0.1:8851/
GET /open/fusion360 -> 307 Location: http://127.0.0.1:51626/
GET /open/kicad     -> 307 Location: http://127.0.0.1:51624/
GET /open/nope      -> 404 {"errorCode":"unknown_bridge"}
GET /open/puppeteer?path=http://evil.com/x -> 307 Location: http://127.0.0.1:8851/   (foreign path refused)
GET /open/puppeteer?path=/health (followed) -> 200 {"status":"ok","bridge":"puppeteer",...}
bridge_list: kicad port=51624 stableUrl=http://127.0.0.1:24680/open/kicad

For the KiCad Bridge side: wherever you hand a human the dashboard URL (the card, the kicad_dashboard-style reply, docs), use the row's stableUrl (http://127.0.0.1:24680/open/kicad) instead of http://127.0.0.1:<port>/. A tab that was already open on a dead port cannot be rescued from outside (no verb can navigate someone's browser tab without driving their machine), which is why the fix is to never give out the moving address in the first place. If kaleymer hits it again, startup.log's PORT-MOVE line will say why the port moved.

John Lauer closed this · 1mo ago
John Lauer · 1mo ago

Confirmed from the KiCad Bridge side on ConfRoomROG (ab 2.1.133, KiCad Bridge 1.0.30): GET http://127.0.0.1:24680/open/kicad answers 307 to http://127.0.0.1:52551/, and bridge_list's kicad row carries stableUrl. Thank you.

On the request to switch the KiCad Bridge's handouts: there are none to switch. The native bridge serves only /status and /command and never puts its own port URL in a reply, a doc or a skill. The KiCad Dashboard in the kicad-bridge repo is a different thing: it runs container-side for the Hydrogen dock, and its direct port URL is the dock's own contract from 2026-08-23 (the dock probes it in the shared port namespace). The page kaleymer had open at :8931 was ab's per-bridge card, so the stableUrl on that card is the whole fix. If a KiCad-side handout ever appears, it will use stableUrl.

Log in to reply.