You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Dashboard plugin pins the relay host to 127.0.0.1, so every panel 502s when the relay isn't on the dashboard's loopback #550
Observed: with the relay healthy and reachable, every panel in the Hermes-Relay dashboard tab (Service status, Paired devices, Last Bridge event, Recent Bridge activity) renders 502 {"detail":"relay unreachable at 127.0.0.1:8767: All connection attempts failed"}. Pairing, the WebSocket, and the Android clients work normally. Only the dashboard tab is affected.
Expected: the dashboard backend honors an env override for the relay host, like the rest of the plugin does.
dashboard/plugin_api.py parameterizes the port but hardcodes the host:
There's no HERMES_RELAY_HOST, so no configuration can redirect it. This is inconsistent with pair.py's read_relay_config(), which already honors RELAY_HOST/RELAY_PORT. The convention exists; the dashboard backend doesn't follow it.
Functional occurrences: line 90 (_RELAY_BASE), 453 (ws:// proxy target), 586 (error detail). Line 937's 127.0.0.1 is an unrelated API-host fallback and should stay. mobile_plugin_api.py and git_api.py have none.
Reproduction steps
Run the relay server so it is not on the same loopback as the Hermes dashboard process (separate container, separate host, or any remote address).
Confirm the relay is healthy: curl http://192.0.2.10:8767/health returns 200.
Confirm the dashboard process reaches it on that address but not on loopback.
Open the dashboard, then the Hermes-Relay tab.
Every panel shows 502 relay unreachable at 127.0.0.1:8767.
Environment
Hermes-Relay version/tag: plugin 1.11.1
Install surface: plugin
hermes-agent version or commit: 0.21.0 (v2026.8.31)
Connection mode: LAN
Sanitized logs, screenshots, or traces
# from the process that runs the dashboard
$ curl -o /dev/null -w '%{http_code}' http://127.0.0.1:8767/health
000
$ curl -o /dev/null -w '%{http_code}' http://192.0.2.10:8767/health
200
# dashboard tab
502: {"detail":"relay unreachable at 127.0.0.1:8767: All connection attempts failed"}
Upstream or standard-path notes
Not an upstream hermes-agent issue. The relay dashboard plugin is relay-only. Standard chat, Manage, and dashboard voice are unaffected; the gateway and API server behave normally.
Thanks for the detailed split-host reproduction! This is confirmed in both the public Plugin v1.11.1 release and current dev: the dashboard backend fixes its HTTP and WebSocket relay hops to 127.0.0.1, and its tests explicitly enforce that loopback destination. The documented trust model also relies on the co-hosted hop for several management routes that allow loopback access without a paired-device bearer; RELAY_HOST configures the relay listener/advertised endpoint, not this dashboard upstream.
That makes the 502 expected for a split-host or split-network-namespace deployment under the current design, while support for that topology is a valid enhancement. A hostname substitution alone would move these calls outside the loopback trust boundary and leave protected routes unauthorized, so this needs an authenticated proxy design rather than only an environment override. A maintainer will follow up to confirm whether split-host dashboard-to-relay operation should be supported and which authenticated transport contract should cover it.
Checked this against the tree. The triage is right, and I'm withdrawing the env-override suggestion from the description.
plugin/relay/server.py:130 describes the trusted loopback hop, _require_loopback() at 1058 gates /pairing/register, /pairing/approve and /pairing/mint, and plugin/dashboard/test_plugin_api.py asserts the outbound host directly: assertEqual(request.url.host, "127.0.0.1") at 244 and 566, plus the error-detail assertion at 695. A host override would fail those tests and turn the gated routes into 403s instead of connection-refused. I hit that already: /relay/security returns 403: loopback only from a non-loopback peer.
The gates behind the failing panels are not uniform, which might narrow the design question:
Only the Bridge panels depend on loopback trust. /health is open and /sessions already has a non-loopback answer in the paired-device bearer. If the status and devices surface accepted a bearer the way a paired client does, that part works without a new transport contract, and the Bridge surface stays behind whatever authenticated proxy design lands.
Not arguing the topology has to be supported. If it should not be, the useful outcome is a docs line saying the dashboard plugin requires a co-hosted relay, since today it surfaces as a generic 502 with nothing pointing at the topology.
Activity
hermes-relay-triage commentedon Sep 7, 2026
🤖 Hermes-Relay automated triage
Thanks for the detailed split-host reproduction! This is confirmed in both the public Plugin v1.11.1 release and current
dev: the dashboard backend fixes its HTTP and WebSocket relay hops to127.0.0.1, and its tests explicitly enforce that loopback destination. The documented trust model also relies on the co-hosted hop for several management routes that allow loopback access without a paired-device bearer;RELAY_HOSTconfigures the relay listener/advertised endpoint, not this dashboard upstream.That makes the 502 expected for a split-host or split-network-namespace deployment under the current design, while support for that topology is a valid enhancement. A hostname substitution alone would move these calls outside the loopback trust boundary and leave protected routes unauthorized, so this needs an authenticated proxy design rather than only an environment override. A maintainer will follow up to confirm whether split-host dashboard-to-relay operation should be supported and which authenticated transport contract should cover it.
FiveBoroughs commentedon Sep 7, 2026
Checked this against the tree. The triage is right, and I'm withdrawing the env-override suggestion from the description.
plugin/relay/server.py:130describes the trusted loopback hop,_require_loopback()at 1058 gates/pairing/register,/pairing/approveand/pairing/mint, andplugin/dashboard/test_plugin_api.pyasserts the outbound host directly:assertEqual(request.url.host, "127.0.0.1")at 244 and 566, plus the error-detail assertion at 695. A host override would fail those tests and turn the gated routes into 403s instead of connection-refused. I hit that already:/relay/securityreturns403: loopback onlyfrom a non-loopback peer.The gates behind the failing panels are not uniform, which might narrow the design question:
/health: no auth gate/sessions(handle_sessions_list):_require_bearer_session/bridge/activity:_require_loopbackOnly the Bridge panels depend on loopback trust.
/healthis open and/sessionsalready has a non-loopback answer in the paired-device bearer. If the status and devices surface accepted a bearer the way a paired client does, that part works without a new transport contract, and the Bridge surface stays behind whatever authenticated proxy design lands.Not arguing the topology has to be supported. If it should not be, the useful outcome is a docs line saying the dashboard plugin requires a co-hosted relay, since today it surfaces as a generic 502 with nothing pointing at the topology.