Skip to content

feat: T3 Connect environments switch to local connections automatically - #5463

Open
t3dotgg wants to merge 5 commits into
mainfrom
t3code/promote-t3-connect-local
Open

t3dotgg wants to merge 5 commits into
mainfrom
t3code/promote-t3-connect-local

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Problem

Connecting to an environment through T3 Connect always routes through the relay tunnel, even when your phone or laptop is sitting on the same network (or tailnet) as the environment. That means tunnel latency for traffic that could be a LAN hop away.

Solution

Relay connections now promote themselves to a direct connection when one exists, and fall back to the relay when it stops working.

  • The server advertises its own reachable endpoints (loopback, LAN, Tailscale) on a new authenticated GET /api/remote-access/endpoints route (relay:read scope), aware of what it is actually bound to. A loopback-only server advertises nothing promotable.
  • While relay-connected, the client fetches those endpoints through the tunnel, probes the direct candidates, and verifies each one is the same environment via the descriptor's environmentId before trusting it.
  • Promotion reuses the cached DPoP access token. The token is not host-bound (only per-request proofs are, and those are minted fresh per origin), so switching routes needs no relay round-trip and no re-bootstrap.
  • A failed direct route clears the override, starts a 5-minute cooldown for that endpoint so a flaky LAN cannot ping-pong the connection, and falls back to the relay within the same connection attempt.
  • The Tailscale endpoint synthesis moved from the desktop app into @t3tools/tailscale so desktop and server advertise identical tailscale-ip: / tailscale-magicdns: endpoint ids.

Browser-hosted HTTPS clients can only promote to HTTPS endpoints (mixed-content rules); that falls out of the probe failing rather than special-cased platform logic. Desktop and mobile can promote to plain LAN HTTP endpoints.

Status

Warning

Theo has not tested this yet. Rebased onto main on 2026-09-01 and the four Bugbot/Macroscope findings (per-endpoint cooldowns, 3s direct probe timeout, IPv6 loopback host, Serve reachability) are fixed with regression tests in 2aa5f83. Typechecks, lint, and the focused test suites pass (promotion candidate selection, resolver direct-route + fallback, supervisor lease replacement, server endpoint resolution across binding modes), but no one has verified the end-to-end promotion flow against a real relay-connected environment.


Built by Claude Fable 5.1 via Claude Code.
🤖 Generated with Claude Code


Note

Medium Risk
Touches connection establishment, DPoP authorization, and background lease replacement; mis-probed or stale advertised endpoints could cause extra reconnects, though descriptor checks and cooldowns limit ping-ponging.

Overview
Relay-connected environments can switch to LAN or tailnet routes automatically when the server advertises reachable direct endpoints and probes confirm the same environmentId.

The server adds GET /api/remote-access/endpoints (relay:read), backed by ServerAdvertisedEndpoints, which lists only addresses the process actually listens on (loopback vs wildcard vs specific bind, Tailscale Serve reachability, IPv6 loopback). Tailscale endpoint synthesis moves into @t3tools/tailscale with a source field so desktop and server share stable tailscale-ip: / tailscale-magicdns: ids; desktop drops its local provider import.

On the client, ConnectionPromotion fetches that list over the tunnel, ranks LAN over private-network candidates, probes with short timeouts, and stores an in-memory override. The relay resolver tries authorizeDpopDirect (cached DPoP token, fresh proofs per origin) before bootstrap; failures clear the override, apply per-endpoint cooldowns, and fall back in the same prepare. The supervisor rediscovers on an interval and reconnects via PromoteRequested without backoff when a better route appears.

Reviewed by Cursor Bugbot for commit 2db8ba7. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add automatic local connection promotion for T3 Connect relay environments

  • Adds ServerAdvertisedEndpoints service and a GET /api/remote-access/endpoints API that returns addresses compatible with the server's binding mode, classified by reachability (LAN, private-network, public), with a 60-second cache
  • Adds ConnectionPromotion service that fetches advertised endpoints through the current relay connection, filters and ranks candidates (LAN first), probes them concurrently with a 3-second descriptor timeout, and stores the selected route override per environment
  • Adds authorizeDpopDirect to RemoteEnvironmentAuthorization which authorizes via a cached DPoP token against a direct endpoint, rejecting missing or near-expiry credentials with ConnectionTransientError
  • EnvironmentSupervisor now periodically discovers direct routes (every 3 minutes via PROMOTION_REDISCOVERY_INTERVAL) for connected DPoP-authenticated relay sessions, signals PromoteRequested to replace the relay lease without backoff, and reports failed promoted routes to clear the override and fall back to relay
  • Moves the Tailscale endpoint provider from the desktop backend into the shared @t3tools/tailscale package and adds a required source parameter to endpoint resolution
  • Behavioral Change: ConnectionResolver.makeRelayBroker now attempts direct authorization through a stored override before relay bootstrap; any direct failure reports the override and falls back to relay in the same prepare call. Failed endpoints enter a 5-minute cooldown in ConnectionPromotion.make. Promotion discovery only runs for relay targets with DPoP-authenticated active connections; non-relay and non-DPoP sessions are unaffected.

Macroscope summarized 2db8ba7.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dcce6d0-0b9f-4fe9-8718-8e48cab2fcd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 6, 2026
Comment thread packages/client-runtime/src/connection/promotion.ts Outdated
Comment thread apps/server/src/remoteAccess/ServerAdvertisedEndpoints.ts Outdated
Comment thread apps/server/src/remoteAccess/ServerAdvertisedEndpoints.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eb5f14b. Configure here.

Comment thread packages/client-runtime/src/connection/promotion.ts
Comment thread packages/client-runtime/src/authorization/service.ts
@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces an automatically enabled local-promotion workflow that changes relay connection behavior, adds background endpoint discovery and route switching, and extends authenticated DPoP use to direct origins. Its cross-cutting production impact and auth-sensitive connection changes merit human review.

You can add or adjust custom eligibility rules. Learn more.

t3dotgg and others added 2 commits September 1, 2026 16:21
Relay-connected environments now discover direct LAN and Tailscale routes
to the same server and reconnect through them automatically, with the
relay as fallback.

- Server: authenticated GET /api/remote-access/endpoints (relay:read)
  advertises the server's own loopback/LAN/Tailscale endpoints, aware of
  the configured binding. Tailscale endpoint synthesis hoisted from
  desktop into @t3tools/tailscale and shared.
- Client: while relay-connected, a scoped discovery fiber fetches
  advertised endpoints through the tunnel, probes direct candidates,
  verifies the environment id, and stores a per-environment route
  override. The supervisor replaces the lease without backoff and the
  relay broker connects through the override using the cached DPoP
  access token (host-independent; only per-request proofs are URL-bound).
  A failed direct route clears the override, starts a cooldown to
  prevent route flapping, and falls back to the relay in the same
  prepare call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Track promotion cooldowns per endpoint so a second failed candidate does
  not clear the first one's cooldown (Bugbot, Macroscope).
- Probe a direct route's descriptor with the 3s cached-endpoint timeout so
  a black-holed LAN address leaves the 15s establish budget for the relay
  fallback (Bugbot).
- Advertise the loopback endpoint at the host the server actually binds
  (`::1`, `127.0.0.2`) instead of assuming 127.0.0.1, and withhold the
  Tailscale Serve endpoint when Serve cannot reach that binding (Macroscope).
- Tag promoted direct sockets with connectionMethod=direct for the
  analytics field main added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/promote-t3-connect-local branch from eb5f14b to 2aa5f83 Compare September 1, 2026 23:24
CI's typecheck fails on the Effect language service's multipleEffectProvide
warning, which the new cooldown test tripped by chaining two Effect.provide
calls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.3 KiB 13.2 KiB −51 B (−0.4%) 15.1 KiB ✅
Codex Thread snapshot wire 6.9 KiB 6.9 KiB +8 B (+0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.4 KiB 6.3 KiB −59 B (−0.9%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 55.6 KiB 55.5 KiB −88 B (−0.2%) 66.4 KiB ✅
Codex Live turn messages 10 8 −2 (−20.0%) 21 ✅
Claude Total thread wire 13.2 KiB 13.2 KiB +93 B (+0.7%) 15.1 KiB ✅
Claude Thread snapshot wire 6.9 KiB 6.9 KiB +5 B (+0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.3 KiB 6.3 KiB +88 B (+1.4%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 55.5 KiB 56.3 KiB +840 B (+1.5%) 66.4 KiB ✅
Claude Live turn messages 8 8 0 (0.0%) 21 ✅

Baseline: 04efa79 · PR result: 2db8ba7 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment thread apps/server/src/remoteAccess/ServerAdvertisedEndpoints.ts
Comment thread apps/server/src/remoteAccess/ServerAdvertisedEndpoints.ts
Comment thread apps/server/src/remoteAccess/http.ts Outdated
Comment thread packages/client-runtime/src/connection/promotion.ts
t3dotgg and others added 2 commits September 1, 2026 16:30
A machine with Docker or VM bridge interfaces enumerated before its physical
LAN could exhaust the five-candidate cap without probing the reachable LAN
address. Probes run concurrently with a 3s timeout, so the cap bought nothing.
Also import the endpoint service as a module namespace to match server.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The relay broker only reports override failures inside prepare. A direct
route that opened but died during socket setup, before readiness, or later in
the session stayed installed, so every retry re-selected the same broken
route instead of falling back to the relay. The supervisor now reports those
failures too, which clears the override and starts its cooldown.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

size:XL 500-999 changed lines (additions + deletions). 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