fix(macos): dashboard window keeps a dead SSH tunnel port after tunnel restart#100488
Conversation
In remote SSH mode the Dashboard window baked the forwarded tunnel's local port into the WebView URL and injected auth at open time; when RemoteTunnelManager recreated the tunnel on a new ephemeral port the open window reconnected to the dead port forever. DashboardManager now subscribes to GatewayEndpointStore and, while the window is open, re-injects auth for the new origin and reloads the WebView without stealing focus. Dashboard log lines now strip the #token= fragment so endpoint changes never write credentials to unified logs. Fixes #100476
OnboardingView.hasCrestodianSetupAuth was removed with the replaced OnboardingView+CrestodianSetup source in #100288, leaving the macOS test target failing to compile on main. Delete the test for the removed internal helper.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6828d525d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| gatewayUrl: Self.websocketURLString(for: dashboardURL), | ||
| token: authToken, | ||
| password: password?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty) | ||
| guard auth.hasCredential, controller.isWindowOpen else { return } |
There was a problem hiding this comment.
Keep tokenless dashboard endpoints in sync
When a remote SSH dashboard is intentionally tokenless (for example gateway.auth.mode = "none") and no device token is cached, auth.hasCredential is false even though the WebView still needs to move to the new forwarded port. This guard drops those .ready updates, so the newly added observer leaves the window or failure page pointed at the dead tunnel until the user manually reopens it; update(url:auth:) already handles empty auth by removing the auth script and loading the URL.
Useful? React with 👍 / 👎.
…l restart (openclaw#100488) * fix(macos): follow gateway endpoint changes in the open Dashboard window In remote SSH mode the Dashboard window baked the forwarded tunnel's local port into the WebView URL and injected auth at open time; when RemoteTunnelManager recreated the tunnel on a new ephemeral port the open window reconnected to the dead port forever. DashboardManager now subscribes to GatewayEndpointStore and, while the window is open, re-injects auth for the new origin and reloads the WebView without stealing focus. Dashboard log lines now strip the #token= fragment so endpoint changes never write credentials to unified logs. Fixes openclaw#100476 * test(macos): drop stale hasCrestodianSetupAuth assertions OnboardingView.hasCrestodianSetupAuth was removed with the replaced OnboardingView+CrestodianSetup source in openclaw#100288, leaving the macOS test target failing to compile on main. Delete the test for the removed internal helper.
Closes #100476
What Problem This Solves
Fixes an issue where remote-mode (SSH transport) users with the macOS Dashboard window open would see the Control UI drop to a permanent "Could not connect" gate after any tunnel churn (sleep/wake is enough). The window baked the forwarded tunnel's local port into the WebView URL and the injected
__OPENCLAW_NATIVE_CONTROL_AUTH__payload at open time; whenRemoteTunnelManagerrecreated the tunnel on a new ephemeral port, the open window never learned the new endpoint and its reconnect loop targeted the dead port forever. Only manually reopening the window recovered.Why This Change Was Made
DashboardManagernow subscribes toGatewayEndpointStore.subscribe()(started when the first dashboard window is created). When a.readystate resolves to a dashboard URL different from the one currently shown while the window is open (or miniaturized), it re-derives the auth payload for the new origin and swaps the WebView via a newDashboardWindowController.update(url:auth:)— the same re-inject-auth-and-reload pathshow(url:auth:)uses, minus window ordering, so a background tunnel restart never steals focus. The failure page also observes, so a recovered tunnel swaps the window back to the live dashboard. While touching these paths, dashboard log lines now strip the#token=fragment (newdashboardLogString(for:)) so endpoint changes never write credentials to unified logs (flagged by pre-land review; the same leak existed in the pre-existing open/load log lines).Also includes one unbreak commit:
main's macOS test target failed to compile because #100288 removedOnboardingView.hasCrestodianSetupAuthbut left its test behind; the stale test is deleted.User Impact
Remote-mode users can leave the Dashboard window open across tunnel restarts: the Control UI reconnects to the new forwarded port automatically instead of dying until a manual reopen. Complements the web-side reconnect banner (#100479), which keeps the UI mounted but cannot fix a stale endpoint by itself. Dashboard auth tokens no longer appear in macOS unified logs.
Evidence
apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift: a.readyendpoint on a new port reloads the open window and re-injects the auth script for the new origin (old port gone from user scripts); an unchanged endpoint and non-.readystates do not re-inject (script identity check); endpoint changes are ignored while the window is closed;dashboardLogStringstrips the token fragment.swift test --package-path apps/macos --parallel: 631 tests in 113 suites passed (this also proves the compile unbreak — the same command fails on currentmainwithtype 'OnboardingView' has no member 'hasCrestodianSetupAuth').swiftformat --lint --config config/swiftformatclean on touched sources.