Repository navigation
feat(cloud-sessions): reach cloud sessions through the Nexi broker (Entra), T3 Connect as fallback - #341
Merged
Conversation
…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>
… 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
marked this pull request as ready for review
October 2, 2026 11:27
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.ymlbroker mode are in pj/nexi-distribution#558 (tracking: nexi-distribution #556).How
The fleet VM dials out to the broker. This machine's server:
broker_grantinstead of the T3 Connect credential handoff;The client resolves the new
BrokerConnectionTargetthe 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_URLis set, andNEXI_FF_CLOUD_BROKER=0switches it off. Without it, every path is the unchanged T3 Connect flow, which stays as the fallback for one release.Surfaces
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).BrokerEnvironmentGatewayresolver branch.Evidence
/oauth/tokenwith the pairing credential → bearer auth).CloudEnvironmentConnectListfailures predate this change; they fail identically onorigin/mainf54d074 with my changes stashed.tscwas 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