Skip to content

fix(ssh): reliable legacy fallback when dashboard /api/ws is unavaila…#714

Merged
fathah merged 2 commits into
mainfrom
fix/ssh-tunnel-chat-fallback-667
Jun 17, 2026
Merged

fix(ssh): reliable legacy fallback when dashboard /api/ws is unavaila…#714
fathah merged 2 commits into
mainfrom
fix/ssh-tunnel-chat-fallback-667

Conversation

@fathah

@fathah fathah commented Jun 17, 2026

Copy link
Copy Markdown
Owner

(#667)

In SSH Tunnel mode the desktop starts hermes gateway and tunnels its port, but the dashboard chat transport speaks WebSocket at /api/ws, which is served only by hermes dashboard (web_server) — never by the gateway. So the dashboard client can never connect over SSH, and ensureClient re-ran the multi-second status+probe on every message before falling back, feeling stuck.

Latch a sticky "dashboard unavailable" flag on the first failed connect for a remote/SSH connection so subsequent messages fall back to the working legacy HTTP transport (/v1/chat/completions through the tunnel) immediately, and fire a one-time toast explaining the limitation. The flag resets on any connection change. Adds renderHook tests for fast-fallback, reset-on-change, and local-stays-retryable.

Also documents the full fix (run a remote hermes dashboard and tunnel it, with the HERMES_DASHBOARD_SESSION_TOKEN the WS actually requires) in docs/ssh-dashboard-transport.md — deferred until it can be tested on a real SSH host.

…ble (#667)

In SSH Tunnel mode the desktop starts `hermes gateway` and tunnels its port,
but the dashboard chat transport speaks WebSocket at `/api/ws`, which is served
only by `hermes dashboard` (web_server) — never by the gateway. So the dashboard
client can never connect over SSH, and `ensureClient` re-ran the multi-second
status+probe on every message before falling back, feeling stuck.

Latch a sticky "dashboard unavailable" flag on the first failed connect for a
remote/SSH connection so subsequent messages fall back to the working legacy
HTTP transport (`/v1/chat/completions` through the tunnel) immediately, and fire
a one-time toast explaining the limitation. The flag resets on any connection
change. Adds renderHook tests for fast-fallback, reset-on-change, and
local-stays-retryable.

Also documents the full fix (run a remote `hermes dashboard` and tunnel it, with
the HERMES_DASHBOARD_SESSION_TOKEN the WS actually requires) in
docs/ssh-dashboard-transport.md — deferred until it can be tested on a real SSH
host.
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a sticky "dashboard unavailable" latch to useDashboardChatTransport so that once /api/ws fails to connect on a remote/SSH connection the hook fast-fails all subsequent ensureClient calls instead of re-running the multi-second startDashboard probe, and fires onDashboardUnavailable exactly once so Chat.tsx can show a toast. The latch resets whenever connectionMode or profile changes.

  • Latch logic (useDashboardChatTransport.ts): dashboardUnavailableRef is set on the first failed startDashboard when connectionMode !== \"local\" && fallbackOnUnavailable; the check is gated by !dashboardUnavailableRef.current so onDashboardUnavailable fires at most once per connection; local connections remain always-retryable.
  • UI wiring (Chat.tsx): a useCallback-stable handleDashboardUnavailable fires a react-hot-toast with a fixed deduplication id; the explicit "dashboard" transport preference (fallbackOnUnavailable=false) is correctly excluded from the latch path so no misleading "using basic chat" toast fires when the turn will actually error.
  • Tests: three new renderHook scenarios cover fast-fallback, reset-on-connection-change, and local-stays-retryable.

Confidence Score: 5/5

Safe to merge; the change is a targeted fallback path that only activates on failed remote dashboard probes and leaves local and explicit-dashboard-preference flows untouched.

The latch is correctly ordered after the already-connected check, correctly cleared on every connection/profile change, and gated by both connectionMode and fallbackOnUnavailable before either writing the ref or calling onDashboardUnavailable. Three new tests verify all the behaviorally important cases.

No files require special attention.

Important Files Changed

Filename Overview
src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts Adds sticky dashboardUnavailableRef latch and onDashboardUnavailable callback; latch sets on first failed startDashboard for non-local connections when fallbackOnUnavailable=true, fast-fails subsequent ensureClient calls, and resets on connectionMode/profile change.
src/renderer/src/screens/Chat/Chat.tsx Wires up handleDashboardUnavailable as a stable useCallback that fires a deduplicated react-hot-toast; toast id prevents duplicates even if callback is invoked more than once.
src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.test.tsx Adds three focused renderHook tests for the new latch: fast-fallback on SSH, reset-on-connection-change, and local-stays-retryable.
src/shared/i18n/locales/en/chat.ts Adds dashboardUnavailableFallback i18n key; message is clear and does not reference any internal issue numbers.
docs/ssh-dashboard-transport.md New design document capturing the root cause of SSH dashboard chat failure and the full future fix design; clearly marked as not yet implemented.

Reviews (2): Last reviewed commit: "Merge main into fix/ssh-tunnel-chat-fall..." | Re-trigger Greptile

Comment thread src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts Outdated
Comment thread src/shared/i18n/locales/en/chat.ts Outdated
Resolve the useDashboardChatTransport.ts conflict in ensureClient by keeping the
SSH fast-fallback/latch logic on top of main's slash/background transport work.

Also addresses the two greptile review findings:
- P1: latch "dashboard unavailable" and fire onDashboardUnavailable only when
  fallbackOnUnavailable is true, so the "using basic chat" toast can't appear in
  the explicit-dashboard transport mode (where the turn errors instead of
  falling back).
- P2: drop the internal "#667" reference from the user-facing toast string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fathah fathah merged commit 1352d8c into main Jun 17, 2026
1 check passed
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.

1 participant