fix(cli,relay): unbreak headless host registration (env key, org selection, relay reason)

Merged
KI
4mo ago
+523-46

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-auth precedence tests (flag > env > config > oauth)
  • resolveOrganization tests — 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 on p.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 in hosts list, no desktop interaction

Open in Stage

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, and hosts set-wake now support --org <id|slug|name> and default to the active org, enabling full headless use.
  • Bug Fixes

    • CLI: SUPERSET_API_KEY is now read in resolveAuth with precedence --api-key > env > config > OAuth.
    • CLI: superset start supports --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").

Summary by CodeRabbit

  • New Features
    • Added --org support to host, status, and startup commands, accepting an organization ID, slug, or name.
    • Added headless authentication through the SUPERSET_API_KEY environment variable.
    • Improved relay access-denied messages with specific explanations.
  • 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.