Skip to content

fix(native): advertise the live WebTransport port and real device name - #38

Open
evanacci wants to merge 2 commits into
mainfrom
cursor/fix-stale-wt-peer-name-5ca9
Open

evanacci wants to merge 2 commits into
mainfrom
cursor/fix-stale-wt-peer-name-5ca9

Conversation

@evanacci

@evanacci evanacci commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Why

Mac app ↔ browser transfers hang after Accept because the Mac advertises a WebTransport endpoint that is no longer listening. Mac↔Mac still works: that path sends wsUrl and falls back to WebSocket when QUIC metadata is stale. Browsers on https://localbolt.app cannot use ws://, so they dial wtUrl + certHash from connection_request / connection_accepted and wait.

wt_info.json lives in the persistent daemon data dir (~/Library/Application Support/LocalBolt/daemon). The app polled until that file existed, then cached wt_port / wt_cert_hash once. A previous launch's file is almost always already there, and the WS port is 9100 + (app pid % 900) with WebTransport on WS+1, so the cached port does not match the daemon that just started. The daemon does rewrite the file (wt_port = ws_port + 1 in bolt-daemon); the shell never read it again.

Separately, the device list showed 0.0.0.0 for a Mac Studio. The UI prints signaling device_name, and registration used Host.localizedName. That is NSHost, which returns the wildcard bind address on some Macs. ?? "Mac" does not run, because the string is non-nil. The browser (localbolt-v3) also displays device_name and does not invent it from wtUrl, so no companion change is required there for the device list.

LAN verify of the device list succeeded: the MacBook now shows the Studio computer name. Two labels on the Studio were still wrong when the MacBook connected:

  1. The daemon ask sheet (PairingRequestView: "Connection Request", Remember this device, Block) showed 0.0.0.0:0 and unknown.
  2. After Accept, the connected session header showed 192.168.4.25:55170 (MacBook LAN IP + ephemeral port) with Approved, not the advertised name (Evan's MacBook Pro).

bolt-daemon ipc/trust.rs sets remote_device_name to from_peer.to_string() and remote_device_type to "unknown". The session loop passes peer_addr (the real ip:port). WS outbound uses a synthetic 0.0.0.0:0. The Mac app copied that string into the ask sheet and, on Accept, overwrote pendingAcceptPeer, so the connected header lost the name signaling already had on connection_request, the peer list, and pendingAcceptPeer.

Fix

  • Snapshot wt_info.json and quic_info.json before spawning the daemon. Advertise a record only when its bytes changed (or the file was created this launch) and the port is this process's WS+1 (WT) or WS+2 (QUIC).
  • Re-read both files immediately before connection_request and connection_accepted. QUIC is omitted when the file is stale, so native peers stay on the existing WebSocket fallback.
  • If a browser accept has no live WT listener, cancel with "WebTransport isn't ready…" instead of sitting in .connecting.
  • Advertise the System Settings computer name (SCDynamicStoreCopyComputerName), then a usable localizedName or hostname. Never register or display 0.0.0.0 / ::. A later real device_name replaces a bind-address entry already in the peer list.
  • Treat socket labels as unusable device names: IP:port (including a real LAN address such as 192.168.4.25:55170 and 0.0.0.0:0), bare IPs, and ::. The ask sheet and session header use the same helper as the device list, so a leftover socket cannot render. A LAN address is still a valid WebTransport host — that check is separate, and wtUrl refresh is unchanged.
  • When building the ask sheet, accepting it, or setting connectedPeer (IPC session-connected and the WebTransport stderr path), prefer pendingAcceptPeer, pendingInitiatorPeer, the in-flight connection_request, or the single discovered peer over a socket name. "unknown" yields to the advertised device type and is omitted when nothing better is known.
  • Accept no longer replaces a known signaling name with the daemon socket string. A socket address is not stored as the TOFU device name.

Discovery wt_url is still whatever was known when signaling registered (the server rejects a second register). localbolt-v3 dials the connection payload, which is refreshed.

Companion change still required in bolt-daemon

This PR does not modify bolt-daemon. A separate agent should change bolt-ecosystem/bolt-daemon so ipc/trust.rs stops writing peer_addr (and the synthetic 0.0.0.0:0) into remote_device_name, and stops sending remote_device_type: "unknown" when the peer advertised a name and type. Until that lands, the Mac app substitutes the signaling identity. The sheet and the connected header show the advertised name (for example Evan's MacBook Pro) instead of the socket.

No localbolt-v3 change is required for either bug.

Verify on a Mac (same LAN, localbolt.app open)

  1. Quit LocalBolt. Confirm a leftover ~/Library/Application Support/LocalBolt/daemon/wt_info.json exists and note wt_port.
  2. Relaunch. Console should log [WT] advertising https://<lan-ip>:<port> with a port that matches the new file (wt_port = daemon WS port + 1), not the leftover.
  3. From the browser, connect to the Mac and send a file. The transfer should start. It should not sit on WebTransport against the old port.
  4. Repeat Mac → browser (Accept on the browser). Same result.
  5. On the other Mac, the Studio should appear as its System Settings computer name, not 0.0.0.0.
  6. Mac ↔ Mac still transfers (QUIC when quic_info.json was rewritten this launch, otherwise WebSocket).
  7. On the Studio, when the MacBook connects, the Connection Request sheet shows the MacBook's advertised computer name and device type, not 0.0.0.0:0 / unknown.
  8. After Accept, the connected session header shows that same name with Approved, not 192.168.4.25:<ephemeral port>.
Open in Web Open in Cursor 

cursoragent and others added 2 commits October 24, 2026 14:57
The Mac app treated any leftover wt_info.json as the daemon listener and
cached that port for signaling. Browsers dial wtUrl from connection_request
and connection_accepted, so a previous launch's port hangs Mac-to-browser
transfers. Ignore endpoint files this daemon did not rewrite, and re-read
them before each advertisement. If no live listener exists, cancel the
browser connect instead of waiting.

Host.localizedName can be the bind address 0.0.0.0. Advertise the System
Settings computer name, and never show a bind address as a peer name when
a real device name is available.

Co-authored-by: Evan Oberfelder <evanoberfelder@gmail.com>
The daemon ask sheet was rendering peer_addr as the device name and
Accept copied that socket string over the signaling name, so the
connected header showed a LAN IP and ephemeral port. Prefer the
advertised name and type, and treat socket labels as unusable display
names without changing which hosts are advertised for WebTransport.

Co-authored-by: Evan Oberfelder <evanoberfelder@gmail.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.

2 participants