fix(cli,relay): unbreak headless host registration (env key, org selection, relay reason)
Summary
Running the host-service headlessly (sandbox, CI, server) via API key was effectively broken — diagnosed end-to-end while bringing a Sprites.dev host online. The daemon ran, minted a JWT, connected the relay, then flapped 1008 Forbidden and never surfaced in hosts list. Three independent bugs, each fixed here. Design/rationale: plans/20260710-headless-host-registration-fixes.md.
1. SUPERSET_API_KEY env is documented but never read
The help text and whoami promise the env var works, but no code read it — SUPERSET_API_KEY=… superset start silently minted with no token → Failed to mint JWT: 401. Folded the env read into resolveAuth's override tier, so precedence is --api-key → env → config.apiKey → OAuth, centralized where the ladder already lives and is tested.
2. superset start had no org selection (used the active org)
A user in multiple orgs registered the host under whichever org was "active" — exactly how the sprite landed under the wrong org. Added --org <id|slug|name>. With multiple memberships it prompts on a TTY and refuses with guidance (never silently guesses) in agent/non-interactive mode.
3. Relay 1008 Forbidden was opaque
checkHostAccess returned a bare bool, so the close frame was just Forbidden — the real cause (not in org / host not registered) was invisible and cost hours. It now returns a denial reason carried into the close frame (not a member of this org / host not registered to this account); tunnel-client already logs the reason.
Not a bug — dropped
The earlier "host stays unclaimed forever" theory was wrong: host.ensure auto-inserts the v2UsersHosts owner link on first registration, so a headless host self-claims to the API key's user. The allowed:false we saw was Bug 2 (wrong org) stacked on the old paidPlan gate (removed upstream by #5571). Corrected in the plan doc.
Test plan
-
resolve-authprecedence tests (flag > env > config > oauth) -
resolveOrganizationtests — id/slug/name match, no-match error, single-org, and both OR branches of the non-interactive guard (mutation-verified: dropping either clause makes the multi-org path hang onp.select) - relay + cli typecheck clean; Biome clean on touched files
- End-to-end:
SUPERSET_API_KEY=… superset start --daemon --org <org>on a fresh headless box → online + named inhosts list, no desktop interaction
Summary by cubic
Fixes headless host registration and management for CI/sandbox by honoring SUPERSET_API_KEY, adding explicit org selection, and surfacing relay denial reasons. All host observe/manage commands now accept --org for use without an active desktop session.
-
New Features
- CLI:
hosts list,status,hosts wake, andhosts set-wakenow support--org <id|slug|name>and default to the active org, enabling full headless use.
- CLI:
-
Bug Fixes
- CLI:
SUPERSET_API_KEYis now read inresolveAuthwith precedence--api-key> env > config > OAuth. - CLI:
superset startsupports--org <id|slug|name>; prompts on TTY when multiple orgs; errors in non-interactive/agent mode instead of guessing. - Relay: Access checks return specific reasons and include them in 403/1008 messages (e.g., "not a member of this org", "host not registered to this account").
- CLI:
Summary by CodeRabbit
- New Features
- Added
--orgsupport to host, status, and startup commands, accepting an organization ID, slug, or name. - Added headless authentication through the
SUPERSET_API_KEYenvironment variable. - Improved relay access-denied messages with specific explanations.
- Added
- Bug Fixes
- Prevented ambiguous or incorrect organization selection in interactive and headless workflows.
- Improved access error details for HTTP and WebSocket connections.
- Documentation
- Added guidance for headless host registration and organization selection.