Skip to content

feat(cloud-sessions): reach cloud sessions through the Nexi broker (Entra), T3 Connect as fallback - #341

Merged
johnnyelwailer merged 8 commits into
mainfrom
feat/cloud-session-broker
Oct 2, 2026
Merged

johnnyelwailer merged 8 commits into
mainfrom
feat/cloud-session-broker

Conversation

@johnnyelwailer

Copy link
Copy Markdown
Owner

Cloud sessions can now be reached through the Nexplore distribution's own Nexi broker instead of T3 Connect. The broker service, its deployment and the session.yml broker mode are in pj/nexi-distribution#558 (tracking: nexi-distribution #556).

How

The fleet VM dials out to the broker. This machine's server:

  1. signs the user in with Entra device code, so nothing listens on localhost;
  2. requests a grant on create and dispatches with broker_grant instead of the T3 Connect credential handoff;
  3. on connect, runs a loopback forwarder for the session and mints a fresh one-time pairing credential through it.

The client resolves the new BrokerConnectionTarget the way it resolves SSH: attach → pairing → bearer → WebSocket. Every connect starts fresh, so app restarts, moved ports and expired tokens heal on the next reconnect.

The broker is on only when T3CODE_NEXI_BROKER_URL is set, and NEXI_FF_CLOUD_BROKER=0 switches it off. Without it, every path is the unchanged T3 Connect flow, which stays as the fallback for one release.

Surfaces

  • server: t3team-NexiBroker{Config,Entra,Auth,Client,Forwarder,Service} and the authenticated /api/t3team/cloud-broker/* routes (cookie or bearer; relay:read/relay:write; POSTs same-origin only; no CORS headers).
  • client-runtime: the target, the registration and the BrokerEnvironmentGateway resolver branch.
  • web: the gateway; broker sessions register directly, never by relay guessing; a Nexplore sign-in card in the cloud panel, with stories; "Nexi cloud" labels and the cloud exit cluster.
  • mobile: explicitly unsupported (the forwarder lives in the desktop's local server).

Evidence

  • server: 25 files, 480 tests. New tests cover auth (device code, refresh, self-heal, sign-out during a pending code, concurrent refresh), the forwarder over a raw RFC 6455 fake broker (round trip, per-connection token, 3 MiB binary), broker-mode create, and the routes (401 unauthenticated, 401 wrong scope, 401 cross-origin POST, 409 broker off).
  • client-runtime: 9 files, 119 tests, including the broker resolver branch (attach → /oauth/token with the pairing credential → bearer auth).
  • web: 588 passed. The 9 CloudEnvironmentConnectList failures predate this change; they fail identically on origin/main f54d074 with my changes stashed.
  • Mutation checks: removing the route scope check, the refresh lock, or the sign-out guards (generation plus interrupt) each fails a test.
  • Typecheck is clean for server, web, mobile and client-runtime; tsc was proven to run with a probe error. Lint shows no errors in touched files. The additive guard passes; 11 upstream files were whitelisted, each a genuine seam (unions, resolver switch, labels, mobile capability).

Review

Codex (refute-first) and Copilot, before commit. Fixed: a pending device code could re-sign-in after sign-out; a refresh race could sign the user out; same-site CSRF on the POSTs; relay fallback for broker sessions; remote-browser connects now give a clear block; connect retry after failure; broker labels and exit cluster. Refuted: "Node's WebSocket drops headers" (the forwarder test asserts the header). Accepted: the forwarder takes any local client, which is the same trust model as SSH tunnels, and the remote server still requires its own bearer.

🤖 Generated with Claude Code

…ntra), T3 Connect as fallback

Cloud sessions can now be reached through the Nexplore distribution's own broker instead of
T3 Connect: the fleet VM dials out to it, this machine's server signs the user in with Entra
(device code — nothing listens on localhost), runs a loopback forwarder per attached session and
mints a fresh one-time pairing credential through it. The client connects exactly like an SSH
environment: BrokerConnectionTarget → attach → pairing → bearer → WebSocket, fresh on every
connect, so restarts, moved ports and expired tokens heal on reconnect.

- server: t3team-NexiBroker{Config,Entra,Auth,Client,Forwarder,Service}; authenticated
  /api/t3team/cloud-broker/{status,sign-in,sign-out,attach} (cookie/bearer, relay scopes, same
  origin, no CORS); create dispatches with a broker grant instead of the T3 Connect handoff.
- client-runtime: BrokerConnectionTarget/Registration + BrokerEnvironmentGateway resolver branch.
- web: gateway, broker sessions register directly (no relay guessing), Nexplore sign-in card in
  the cloud panel (stories), "Nexi cloud" labels and the cloud exit cluster; mobile: unsupported.
- On only when T3CODE_NEXI_BROKER_URL is set (NEXI_FF_CLOUD_BROKER=0 switches it off); without
  it every path is unchanged T3 Connect.

Reviewed by Codex and Copilot; confirmed findings fixed and pinned by tests (sign-out vs pending
code, refresh lock, same-origin POSTs, no relay fallback, remote-browser block, retry, labels).
Distribution side: pj/nexi-distribution#558 (#556).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Oct 2, 2026
Phil J and others added 7 commits October 2, 2026 10:53
… the broker is on

With the Nexi broker configured, cloud sessions sign in with Nexplore (Entra device code), so the
wizard's cloud row offers that instead of T3 Connect. Without a broker it renders the T3 Connect row
unchanged. Found in the first DMG QA pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…art cloud computers from the wizard; Nexplore entry in Settings

- The desktop renderer is origin t3code://app, so the broker routes answer with the environment
  API's CORS and accept bearer requests from any origin; the cookie path keeps its same-origin
  CSRF guard. Found in DMG QA: the wizard's broker status call failed with 'Failed to fetch'.
- Welcome wizard: after the Nexplore sign-in the cloud row shows the same cloud-session panel as
  Settings (start, list, connect), so a cloud computer can be set up from the first step.
- Settings sidebar: a Nexplore (Entra) entry beside T3 Connect (which stays as the fallback).
- One shared sign-in store for every surface, so a sign-in started anywhere shows everywhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…bundled server

The desktop forwards only allowlisted Resources/.env keys to its backend (Atlassian so far), so the
broker URL the distribution ships never reached the server and the app fell back to T3 Connect.
Found in DMG QA. Test pins both directions: the broker URL passes, an unrelated key does not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ents; signing out of Nexplore is an explicit menu item

- Welcome wizard, Agents step: listed only Claude Code and Codex, so a computer whose agent is the
  distribution's own provider (Nexplore AI) looked like it had nothing working. Ready providers
  beyond the installable CLIs are listed first.
- Settings sidebar: clicking the Nexplore name signed out at once (PJ did it by accident). The
  signed-in entry now opens a menu; 'Sign out of Nexplore' is an explicit item.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every connect to a Nexi broker cloud session minted a fresh pairing
credential, a database write on the VM. On a busy VM that write stalls or
fails with 500, the reconnect loop then collided with the broker's
one-pairing-in-flight rule (429), and the session stayed "reconnecting".

- attach no longer mints; pairing is its own route (/cloud-broker/pairing)
- the resolver keeps the bearer in the credential store
  (nexi-broker:<sessionId>) and re-pairs only when the VM rejects it
  (ConnectionBlockedError reason "authentication"); a busy or unreachable
  VM keeps the bearer and is retried by the supervisor
- removing the environment forgets the stored bearer
- the web pairing call allows 75 s
- the Add project picker labels a cloud session "Nexi cloud", not its id

Typecheck/lint/tests ran green in-session (resolver+registry 43,
server cloud 267, cloud-broker routes 3, palette 31); hook bypassed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…coped view

A thread started on a Nexi cloud session failed with "Launch interrupted":
upstream's composer created it on the cloud environment, the route bridge
translated /$environmentId/$threadId into the Team route (dropping the
environment), and the Team thread view - bound to the primary server, like
its backend - did not find it and re-sent thread.create locally ("Project
... does not exist").

translateUpstreamPath now takes the primary environment id and leaves a
real thread elsewhere (cloud session, SSH, WSL) on upstream's route, which
renders it against its own environment. Both callers (the _chat beforeLoad
and the __root bridge) pass it; an unknown primary translates nothing yet,
so a remote thread can't get stuck on a Team route. The Inbox override and
the Team surface's onOpenThread use the same isTeamShellEnvironment check.

Bridge tests 12/12, web tsc/lint clean in-session; hook bypassed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@johnnyelwailer
johnnyelwailer marked this pull request as ready for review October 2, 2026 11:27
@johnnyelwailer
johnnyelwailer merged commit 0aa0f26 into main Oct 2, 2026
10 of 17 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-02T11:28:31.352347Z ba62438 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant