Skip to content

[Bug]: Opening a preview in the system browser uses the raw local URL instead of the active expose tunnel URL #194

Description

@pedroraja

Platform

macOS

Operating system version

macOS Tahoe 26.5.1 (25F80)

System architecture

ARM64 (M1, M2, etc)

PolyScope Version

0.24.1

Bug description

Bug description

When connected to a remote Polyscope server, the embedded preview correctly starts the expose tunnel and shows the tunnel URL. Opening that same preview in the system browser (⌘-click on the preview URL) instead hands the browser the raw preview.url from polyscope.json.

On a remote setup that URL is unreachable by definition — it only resolves and only listens on the server machine — so the browser lands on an error page while a perfectly good tunnel is already running.

Expected: the system browser opens the active tunnel URL (https://<subdomain>.<server>.sharedwithexpose.com), the same one the embedded preview is using.

Actual: the system browser opens https://<workspace>.test, which does not resolve on the client machine (and would not be reachable even if it did — Herd's nginx listens on 127.0.0.1 only).

Setup

  • Server: desktop app embedded server (polyscope-server serve) on a Mac mini, with Laravel Herd serving the sites.
  • Client: Polyscope desktop app on a MacBook, connected to the mini through the relay (Servers list).
  • Account: subscribed: true; /api/user returns a valid expose_token and 6 expose_servers.
  • Repo polyscope.json:
{
  "preview": {
    "url": "https://{{folder}}.test"
  }
}

No preview.tunnel: false opt-out is set.

Everything else in the chain works

This is not a tunnel failure. While the embedded preview is open, the expose process is alive on the server machine and the tunnel URL loads the site correctly from the client when pasted into the browser by hand. The token, the expose binary download, the relay connection and the tunnel itself are all fine — only the URL handed to the system browser is wrong.

Suggested fix

In the open-in-system-browser action, resolve the URL through the same path the embedded preview uses: if a tunnel is active for the worktree (getTunnelUrl), open that; otherwise fall back to the raw preview.url. Optionally start the tunnel on demand when the client is remote, so ⌘-click works even if the embedded preview was never opened.

Note

The tunnel subdomain is regenerated on every tunnel start (crypto.randomBytes(8).toString("hex")), so the tunnel URL is not stable across restarts — which rules out bookmarking it as a workaround.

Steps to reproduce

Steps to reproduce

  1. Run Polyscope on machine A (the server) with a workspace whose polyscope.json defines a preview.url that is only reachable on machine A — e.g. a Herd/Valet .test host.
  2. From machine B, connect to that server through the relay (Servers list).
  3. Open the embedded preview for the workspace. ✅ A tunnel starts and the embedded browser shows the expose URL — this part works correctly.
  4. With that tunnel still running, ⌘-click the preview URL to open it in the system browser.
  5. ❌ The system browser opens https://<workspace>.test instead of the tunnel URL, and fails to load.

Relevant log output

## Relevant log output


[tunnel] starting tunnel for <worktreeId> → https://<workspace>.test:443 (provider: expose)
[tunnel:expose] running: /Users/<user>/.polyscope/bin/expose-darwin-arm64 share https://<workspace>.test:443 --subdomain=<REDACTED> --auth=<REDACTED> --server=free --server-host=eu-2.sharedwithexpose.com
[tunnel:expose] tunnel ready at https://<REDACTED>.eu-2.sharedwithexpose.com


Confirmed on the server machine while the embedded preview was open — the tunnel process is alive
and serving:


$ ps -o args= -p <pid>
/Users/<user>/.polyscope/bin/expose-darwin-arm64 share https://<workspace>.test:443 \
  --subdomain=<REDACTED> --auth=<REDACTED> --server=free --server-host=eu-2.sharedwithexpose.com


The ⌘-click happens after these lines and produces no further tunnel log entries — it never queries
the tunnel it already has.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions