Skip to content

fix(native): leave Establishing when a WebTransport session is up - #39

Open
evanacci wants to merge 1 commit into
cursor/fix-stale-wt-peer-name-5ca9from
cursor/fix-wt-establishing-stuck-8dad
Open

evanacci wants to merge 1 commit into
cursor/fix-stale-wt-peer-name-5ca9from
cursor/fix-wt-establishing-stuck-8dad

Conversation

@evanacci

Copy link
Copy Markdown
Contributor

Why

Studio Chrome (https://localbolt.app) can finish a WebTransport session with the Mac daemon while LocalBolt.app stays on Establishing secure connection… / Waiting for encrypted channel.

Oct 24, 2026 LAN capture (Studio → MacBook, native-full daemon log, pairing AllowAlways, SAS 5BE04E):

  • Daemon logged a good browser session-key, then [WT_HELLO] … HELLO ok, [SAS] 5BE04E, [WT_SESSION] … session established, entering shared session loop, and [SESSION] … active session handle registered.
  • The browser reached Approved / Drop files with the same SAS.
  • The Mac app, after Accept, stayed on Establishing for more than 15s. Quitting the app later logged [IPC_CLIENT_DISCONNECTED] and tore that session down.

HELLO success is not the session. The UI is supposed to advance when the shared session handle is registered.

Root cause

The WebTransport answerer (wt_endpoint.rs) does not emit session.connected or session.sas. The WebSocket answerer does, and the Mac IPC handler (daemon://session-connected) only runs for that path. The shell’s fallback watches daemon stderr.

That parser required the substring ACTIVE_SESSION registered / ACTIVE_SESSION cleared. Those are the Rust static’s name, not log lines. bolt-daemon prints:

  • [WT_SESSION] <peer> session established, entering shared session loop
  • [SESSION] <peer> active session handle registered
  • [SESSION] <peer> active session handle cleared

None of the Oct 24 lines contain ACTIVE_SESSION registered, so wtSessionActive never latched and the SwiftUI onChange never called markConnected(). The shell stayed in .connecting, which is the acceptor view “Waiting for encrypted channel”.

[WT_HELLO] … HELLO ok and the earlier [WT_SESSION] session accepted lines are intentionally not a session. Treating them as one would leave Establishing during the ask sheet, or stick on Connected if the user then denied.

Fix

  • Parse the real lifecycle lines (latest token in the stderr ring wins, so a stale cleared does not poison a reconnect). The old ACTIVE_SESSION token still matches if a build ever printed it.
  • Latch across polls. The bridge keeps at most 1000 lines; the poll reads that ring so a handshake burst cannot scroll the session line away before the first look. Diagnostics still show the last 40 lines.
  • When the latch goes up, leave .connecting (and .requesting, if connection_accepted has not moved the initiator yet) for .connected, keep the signaling device name, and show the stderr SAS as unverified. .incomingRequest is unchanged, so a handshake cannot skip Decline.
  • A later active session handle cleared drops the latch and leaves the connected screen. An in-session Approve or mismatch alert is not rewritten back to unverified by the next poll.
  • task(id:) also promotes if the latch was already set while the pairing sheet was up.

No bolt-daemon change is required for this bug. Emitting session.connected / session.sas from the WT answerer would also reach the existing IPC handler; the shell no longer depends on that.

Stacks on #38 so the live WebTransport port and advertised device name stay in this build.

Verify on a LAN (Studio web ↔ Mac app)

This environment cannot run LocalBolt.app. The parser is covered by cargo test in native/shared against the Oct 24 log shape (5BE04E, WT established, shared active session handle registered, and the lines that must not count).

On two machines on the same LAN:

  1. Quit LocalBolt.app on the Mac, then launch this build. Confirm the daemon is the bundled sidecar (or BOLT_DAEMON_PATH) and stderr is the native-full log.
  2. From Studio Chrome at https://localbolt.app, connect to the Mac and Accept on the Mac (signaling Accept, then the daemon Connection Request sheet if it appears). Allow once or Remember this device.
  3. Within about a second of the daemon logging [WT_SESSION] … session established and [SESSION] … active session handle registered, the Mac must leave “Waiting for encrypted channel”.
  4. The Mac should show the connected session and the same SAS as the browser (for example 5BE04E), with Approve this session. That comparison is still required; pairing Allow does not skip it. After Approve, Drop files / Select Files should appear, matching the browser.
  5. The device name should stay the advertised name from fix(native): advertise the live WebTransport port and real device name #38, not a socket address. A missing name still renders as Mac.
  6. Quit the Mac app. The daemon should log active session handle cleared / IPC_CLIENT_DISCONNECTED, and a relaunch should not remain on the old connected screen.
  7. Mac ↔ Mac transfer still works. WebSocket sessions already emit session.connected; this change does not replace that path.
Open in Web Open in Cursor 

Browser peers never emit session.connected. The Mac shell was supposed to
notice bolt-daemon stderr, but it matched the Rust static name
ACTIVE_SESSION registered, which the daemon does not print. The live lines
are [WT_SESSION] … session established and [SESSION] … active session handle
registered, so the UI stayed on Waiting for encrypted channel after the
session was already up.

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