Skip to content

fix(macos): dashboard window keeps a dead SSH tunnel port after tunnel restart#100488

Merged
steipete merged 2 commits into
mainfrom
claude/serene-montalcini-88959f
Jul 5, 2026
Merged

fix(macos): dashboard window keeps a dead SSH tunnel port after tunnel restart#100488
steipete merged 2 commits into
mainfrom
claude/serene-montalcini-88959f

Conversation

@steipete

@steipete steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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; when RemoteTunnelManager recreated 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

DashboardManager now subscribes to GatewayEndpointStore.subscribe() (started when the first dashboard window is created). When a .ready state 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 new DashboardWindowController.update(url:auth:) — the same re-inject-auth-and-reload path show(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 (new dashboardLogString(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 removed OnboardingView.hasCrestodianSetupAuth but 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

  • New tests in apps/macos/Tests/OpenClawIPCTests/DashboardWindowSmokeTests.swift: a .ready endpoint 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-.ready states do not re-inject (script identity check); endpoint changes are ignored while the window is closed; dashboardLogString strips 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 current main with type 'OnboardingView' has no member 'hasCrestodianSetupAuth').
  • swiftformat --lint --config config/swiftformat clean on touched sources.
  • Codex autoreview (gpt-5.5): one accepted finding (token fragment in the new log line) fixed; final run clean.

steipete added 2 commits July 5, 2026 16:01
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.
@openclaw-barnacle openclaw-barnacle Bot added app: macos App: macos size: S maintainer Maintainer-authored PR labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@steipete
steipete merged commit 655b18c into main Jul 5, 2026
62 of 64 checks passed
@steipete
steipete deleted the claude/serene-montalcini-88959f branch July 5, 2026 23:06
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: macos App: macos maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: macOS Dashboard window keeps a dead SSH tunnel port after tunnel restart (remote mode)

1 participant