Skip to content

fix(studio-desktop): probe ports with node:net, not Bun.listen#615

Open
Necmttn wants to merge 2 commits into
mainfrom
fix/614-bug-studio-desktop-backend-never-spawns
Open

fix(studio-desktop): probe ports with node:net, not Bun.listen#615
Necmttn wants to merge 2 commits into
mainfrom
fix/614-bug-studio-desktop-backend-never-spawns

Conversation

@Necmttn

@Necmttn Necmttn commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Fixes #614.

Bug

AxDaemonArbitration.portFree probed port availability with Bun.listen, but the Electron main process runs under Node, not BunBun is undefined there. Every bind probe threw → probePortsFree was permanently falsedecideArbitration could never return spawn:

arbitration decided { mode: 'conflict' }
ERROR: daemon arbitration conflict: ports occupied by an unhealthy process; not starting backend
ERROR: ingest run failed: ECONNREFUSED 127.0.0.1:1738

…with lsof showing nothing on 8521/1738. So on a clean machine the app never started its own backend; the packaged app surfaced the conflict via a bootstrap modal and crashed.

Fix

Use Node's net.createServer().listen({ host: "127.0.0.1", port, exclusive: true }) — the runtime that actually hosts this code — resolving false on bind error. Total, never fails. The attach path (HTTP probes) was unaffected, which is why pre-starting a CLI daemon worked around it.

Verify

  • Arbitration suite green (8/8); tsc clean.
  • node:net correctly detects free vs occupied ports under the Node runtime (a held :8521 reports EADDRINUSE).

Note: this is one of two issues behind the desktop crash. The other — the installed app failing with Cannot find module 'electron-updater' — is a stale build; current source already bundles electron-updater (require('electron-updater') count = 0 in a fresh main.cjs). A rebuilt app picks up both.

🤖 Generated with Claude Code

The Electron MAIN process runs under Node, not Bun, so `Bun` is `undefined`
there. `AxDaemonArbitration.portFree` used `Bun.listen`, so the bind probe threw
on every call and `probePortsFree` was permanently `false`. With `portsFree`
always false, `decideArbitration` could never return `spawn` - on a clean machine
it returned `conflict` ("ports occupied by an unhealthy process; not starting
backend") and the app never started its own surreal + ax serve. The packaged app
then surfaced that conflict via a modal during bootstrap and crashed.

Use Node's `net.createServer().listen({ exclusive: true })` instead (runtime that
actually hosts this code), resolving `false` on bind error. The `attach` path was
unaffected (HTTP probes), which is why pre-starting a CLI daemon was a working
workaround.

Verified: arbitration suite green; node:net detects free vs occupied ports under
the Node runtime (a held :8521 reports EADDRINUSE / occupied).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying ax with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7bfe1ba
Status: ✅  Deploy successful!
Preview URL: https://ca86350c.ax-62d.pages.dev
Branch Preview URL: https://fix-614-bug-studio-desktop-b.ax-62d.pages.dev

View logs

Snapshot for migrating fix/614 to a faster remote box. Captures branch state,
the studio-desktop bug cluster (#615 open, #616 open), and the concrete next
steps (merge #615, fix #616 packaging, build the DB watchdog).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(studio-desktop): backend never spawns — Bun.listen undefined in Electron main → arbitration always 'conflict' → crash

1 participant