Repository navigation
Description
Summary
When expose_host_service_via_relay is off for a host (the default — confirmed in ~/.superset/local.db's settings table), that host never opens its relay tunnel, so hosts_list (and the mcp__superset__* MCP tools, and presumably the mobile/web app) show it as online: false indefinitely. This is arguably correct behavior — the setting is an intentional opt-in for remote reachability — but nothing in the API response, CLI, or MCP tool descriptions distinguishes "relay disabled by local setting" from "genuinely unreachable/crashed."
Why this is confusing
hosts_listreturns a bare{ online: false }with no reason field.- Restarting
host-service(or the whole app) doesn't change this, since the gate is the local setting, not process health — so the natural troubleshooting step (restart it) looks like it silently fails. - The only way I found to confirm the actual cause was extracting the Electron
app.asarand readingdist/main/index.jsto find:and then confirmingconst exposeViaRelay = row?.exposeHostServiceViaRelay ?? false; if (exposeViaRelay && effectiveRelayUrl) childEnv.RELAY_URL = effectiveRelayUrl;
expose_host_service_via_relaywas NULL in the local sqlite settings table. - This matters in practice for anyone driving
mcp__superset__workspaces_*/terminals_*from an agent running locally on the same machine as the host — those calls are host-scoped and silently fail/hang for a host with relay disabled, with no indication in the tool description or error that this is the cause.
Suggested improvements (any one of these would help)
hosts_list(and the CLI'ssuperset hosts list) include arelayEnabled/relayConfiguredfield distinct fromonline, so "off by choice" is distinguishable from "down."- Surface the setting more discoverably — e.g.
superset statusorsuperset hostsmentioning relay is disabled and how to enable it, rather than requiring a settings-UI hunt or reverse engineering. - MCP tool descriptions for host-scoped tools (
workspaces_create,terminals_*, etc.) mention that they require the target host's relay to be enabled, and/or return a clear error (rather than failing/hanging) when it isn't.
Workaround
For anyone hitting this: the local superset CLI (~/.superset/bin/superset) talks directly to http://127.0.0.1:48214 and works regardless of relay status — useful for host-scoped operations on the machine an agent is already running on.
Environment
- Superset Desktop 1.18.3, macOS (arm64)
Activity
[-]hosts_list / MCP tools give no signal that a host's relay is disabled by default[/-][+]hosts_list / MCP tools return "host: offline" when working on local machine[/+][-]hosts_list / MCP tools return "host: offline" when working on local machine[/-][+]hosts_list / MCP tools return "host: offline" when working on local host[/+][-]hosts_list / MCP tools return "host: offline" when working on local host[/-][+]hosts_list / MCP tools return "host: offline" when working on a local host[/+]github-actions commentedon Aug 4, 2026
Summary
The report is accurate, and the code confirms both halves of it: the behavior is intentional, and there is currently no signal anywhere in the cloud data model, API, CLI, or MCP surface that distinguishes "this host's relay is off by local setting" from "this host is down."
v2_hosts.is_onlineis a pure relay-presence bit, and the setting that gates the relay (expose_host_service_via_relay) lives only in the host machine's local SQLite and is never reported upward — so the two states are genuinely indistinguishable to any remote consumer, not merely under-labelled.Affected code
The gate itself:
apps/desktop/src/main/lib/host-service-coordinator.ts:769-770readssettings.exposeHostServiceViaRelayfrom the local DB, and:821-826deletesRELAY_URLfrom the child env when it's off (explicitly re-deleting it after the shell-env merge).false:apps/desktop/src/shared/constants.ts:59(DEFAULT_EXPOSE_HOST_SERVICE_VIA_RELAY), which matches theNULLcolumn you found.packages/host-service/src/serve.ts:106-115only callsconnectRelaywhenenv.RELAY_URLis set.host.ensure— cloud host registration — lives inside that same relay path (packages/host-service/src/tunnel/connect.ts:20), and it's the only production caller (packages/trpc/src/router/host/host.ts:79-90is the onlyinsert(v2Hosts)in the repo). So a relay-disabled host never re-registers or refreshes its row either; it only appears inhosts_listat all because it registered during some earlier period when relay was on.Where
onlinecomes from:apps/relay/src/tunnel.ts:315— the relay is the only writer ofhost.setOnline, on tunnel open/close.apps/api/src/app/api/hosts/jobs/sync-presence/route.ts:91-106— the reconcile job derivesis_onlinepurely from the relay directory zset.packages/db/src/schema/schema.ts:446-473—v2_hostshasis_online,wake_command, names, timestamps. There is no relay-enablement column.Where it surfaces:
packages/trpc/src/router/host/host.ts:54-60—host.listreturns{ id, name, online, wakeCommand, organizationId }, no reason field.packages/mcp/src/tools/hosts/list.ts:13forwardshost.listverbatim;packages/sdk/src/resources/hosts.ts:31-37mirrors the same shape;packages/cli/src/commands/hosts/list/command.ts:29renders it.packages/cli/src/commands/status/command.tsreports local manifest/pid/health only and never mentions relay — which is exactly why restarting looks like a silent no-op.Root cause
Two independent things collapse onto one boolean:
exposeHostServiceViaRelayis host-local state (packages/local-db/src/schema/schema.ts, exposed only over Electron IPC atapps/desktop/src/lib/trpc/routers/settings/index.ts:646-680) that is never transmitted to the cloud.is_onlineis written exclusively by the relay as a function of tunnel presence.So relay-off ⇒ no
RELAY_URL⇒ no tunnel ⇒is_onlinestaysfalsepermanently, and restarting host-service can't change it becausebuildEnvre-reads the same setting on every spawn. There is no bit in the cloud schema that could carry the distinction even ifhost.listwanted to report it — any fix needs the host to report relay-enablement upward (see below).One correction to the report, since it affects where a fix belongs: host-scoped MCP tools do not hang or silently fail — they fail fast with a 503.
apps/relay/src/index.ts:215-217returns a tRPCSERVICE_UNAVAILABLE/"Host is not online"for/trpcpaths (apps/relay/src/index.ts:210returns{"error":"Host not connected"}for non-tRPC),packages/mcp/src/host-service-client.ts:46-50wraps that intoHost <id> returned 503 for <procedure>: …, andpackages/mcp/src/define-tool.ts:137-150returns it asisError: true. ThemaybeReplayfly-replay path (:120-149) returnsnullwhen the directory has no owner, so there's no waiting. The agent therefore does get "Host is not online" — it just gets no hint that the cause is a local opt-in setting rather than a dead machine. If you actually observed a hang (rather than a fast unhelpful error), that's a distinct bug worth its own report with the tool name and duration.Worth noting there's already precedent for the fix you're asking for, on the one surface that can read the local setting:
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/components/RelayOfflineNotice/RelayOfflineNotice.tsx:40-75renders "This device isn't connected to the Superset relay… Enable relay access…" — but it infers this fromonline === falsefor the local host id, a shortcut unavailable to CLI/SDK/MCP callers. Similarly,superset hosts listalready printslocalinstead ofnofor the local machine (command.ts:29), which is itself evidence that theonlinecolumn is known to be ambiguous here.Reproduction
NULL). The mutation restarts host-service (apps/desktop/src/lib/trpc/routers/settings/index.ts:653-680).superset hosts list→ that machine showslocal;host.list/hosts_list→online: false, no reason.workspaces_list/terminals_listwith thathostId→Host <id> returned 503 … "Host is not online".buildEnvre-reads the setting each spawn.http://127.0.0.1:<port>still works (your workaround) — it bypasses the relay entirely.Unit-level sketch:
apps/desktop/src/main/lib/host-service-coordinator.test.tscurrently has no coverage of theRELAY_URLgate (no match forRELAY_URL,exposeHostServiceViaRelay, orbuildEnv). Abun:testcase that stubs the settings row as{ exposeHostServiceViaRelay: null }withRELAY_URLpresent inprocess.env, callsspawn, and assertschildEnv.RELAY_URL === undefinedwould pin the current behavior and give the fix a regression test.Suggested fix direction
The minimal version of suggestion (1) requires a schema change, because the cloud has nowhere to put the answer:
relay_enabled(orrelay_opt_in) column tov2_hostsand have the host report it on every host-service start — not only fromconnectRelay. Concretely: pass the setting intobuildEnvas an explicit env var (rather than encoding it as "RELAY_URLpresent or absent"), and move thehost.ensurecall inpackages/host-service/src/tunnel/connect.ts:20out to an unconditional startup step inserve.tsthat sendsrelayEnabled. That also fixes the related gap that relay-disabled hosts never refresh their cloud row at all. Thenhost.listcan return{ online, relayEnabled, lastSeenAt }and both CLI andhosts_listcan render "off by choice" distinctly.hostServiceCallerror text to say why a host may be unreachable (Host is not online — the host must be running and have relay access enabled in Settings → Security on that device,apps/relay/src/index.ts:216+packages/mcp/src/host-service-client.ts:46-50), and add the same sentence to the host-scoped MCP tool descriptions (workspaces_*,terminals_*,agents_*,projects_list) and toapps/docs/content/docs/mcp-server.mdx, which currently never mentions the relay.superset status(packages/cli/src/commands/status/command.ts) already reads the local manifest and is the natural place to print relay state and the enable path; it could read the desktop setting or the presence of the relay tunnel and add a line likerelay: disabled (Settings → Security, or 'superset start --daemon' on a dedicated host).