A control plane outside the normal traffic path
The relay is T3 Connect's hosted control plane. It links accounts to environments, provisions endpoints, and issues short-lived credentials. After connection, normal API and WebSocket traffic goes directly between the client and environment.
The relay establishes trust but does not carry normal traffic
The relay is the hosted control plane for T3 Connect. It helps clients discover and
connect to remote environments, manages the cloud-side records those connections need,
and delivers optional mobile notifications and Live Activities (infra/relay/README.md:6-8). It stores control-plane decisions and records: which accounts and environments are
linked, which endpoint belongs to each environment, and which credentials remain
valid.
Its defining constraint is a refusal.
"The relay is intentionally not in the hot path for normal T3 Code traffic. After a
client connects, regular API and WebSocket traffic goes directly between that client
and the selected environment"
(infra/relay/README.md:10-11). The relay handles connection
setup once. The client and environment then exchange sessions, streaming diffs, and
chart payloads directly.
The second boundary is credential separation. The environment server and the relay
hold "separate credentials and trust boundaries" (infra/relay/README.md:27-29), a split written down in detail in
docs/internals/environment-auth.md. The relay can establish
the connection, but it cannot use an environment credential after setup.
Back to the big picture to see where this plane sits beside the event spine and the exchange.
Short-lived everything
Every credential in this boundary is measured with a clock. The relay's client access
tokens are DPoP-bound: each carries a cnf.jkt proof-key
thumbprint (infra/relay/src/auth/RelayTokens.ts:183), so a
stolen token cannot be replayed without the matching private key. The docs put it
plainly: relay-brokered clients use this mode
"so that a leaked token cannot be replayed without the corresponding key"
(docs/internals/environment-auth.md:91-93).
EnvironmentConnector.ts:420,575
environment-auth.md:95-103
RelayTokens.ts:29
environment-auth.md:71-74
RELAY_DPOP_ACCESS_TOKEN_TTL = "30 minutes". Scopes are
capped per client: mobile may hold environment:connect, environment:status, and
mobile registration; web gets connect and status only (infra/relay/src/auth/RelayTokens.ts:29,63-73).
infra/relay/src/auth/RelayTokens.ts:26-42,122-139).
t3-env:<environmentId>, scope
environment:status or environment:connect, valid for 2 minutes. The environment
answers with its own signed proof over a fresh nonce (infra/relay/src/environments/EnvironmentConnector.ts:419-464,575-611).
infra/relay/src/environments/EnvironmentCredentials.ts:87-105,151-175).
POST /api/auth/websocket-ticket. The client authenticates
with its real credential in headers, then puts only the ticket in the socket URL, so
long-lived tokens and cookies never appear in a URL (docs/internals/environment-auth.md:95-103).
docs/internals/environment-auth.md:59-78).
On the way in, the relay itself verifies two kinds of bearer credential: Clerk
template JWTs generated from the t3-relay template with the
shared t3-code-relay audience, and Clerk OAuth tokens issued
to the CLI. verifyRelayClientBearerToken tries the template
path first and falls back to OAuth (docs/internals/t3-connect.md:6-11). Its protected-resource metadata then advertises the contract: token endpoint
/v1/client/dpop-token, grant type token-exchange, ES256
signing, and dpop_bound_access_tokens_required: true (infra/relay/src/http/Api.ts:355-385).
One stack, four moving parts
The relay deploys as a single Alchemy stack named
T3CodeRelay, built from the Axiom, Cloudflare, and Drizzle
providers. It yields the schema snapshot, the Hyperdrive binding, the managed endpoint
and API DNS zones, the observability resources, and the Worker itself (infra/relay/alchemy.run.ts:15-28).
Cloudflare Worker
infra/relay/src/worker.ts wires the Cloudflare bindings,
runtime layers, queues, and HTTP API groups: health, metadata, mobile, client,
token, dpop, and server handlers (infra/relay/README.md:31-43).
Two queues ride along: an APNs delivery queue and its dead-letter partner. Batch and
message processing run under spans named
relay.apn_delivery_queue.process_batch and
relay.apn_delivery_queue.process_message (infra/relay/src/worker.ts:122-123,245-260). Mobile push is inherited upstream surface area; this fork keeps it wired but
does not develop it.
Diagnostics in Axiom
One OpenTelemetry trace dataset,
t3-code-relay-traces-prod, is shared by the Worker, the
mobile app, and first-party relay clients. Three dataset-scoped ingest tokens are
write-only per producer, and a
t3-code-relay-recent-spans-prod view holds recent request
and endpoint spans (docs/operations/relay-observability.md:5-12).
Relay-specific annotations land under attributes.custom,
and relay.operation is one of them (docs/operations/relay-observability.md:46-48). Responders are told to prefer the view or APL queries over tailing the Worker
(docs/operations/relay-observability.md:50-53).
Postgres behind Access
The database lives on a self-hosted origin behind Cloudflare Access, reached through
a Cloudflare Tunnel. Access supplies the origin credential, so the Hyperdrive config
declares no port at all; the tunnel's TCP ingress owns it (infra/relay/src/db.ts:48-52).
Local development reaches the same database through its own tunnel session onto
127.0.0.1:5432 (infra/relay/src/db.ts:77-89).
Caching, deliberately off
Hyperdrive can cache reads, and for this database the answer is no:
"The relay reads credentials, allocations and DPoP proofs. A stale read here is an auth bug, so this cache stays off."
That comment sits directly above
caching: { disabled: true } in the Hyperdrive binding,
with an origin connection limit of 20 (infra/relay/src/db.ts:71-76). The schema is Drizzle with dialect postgres and migrations under
infra/relay/migrations/postgres (infra/relay/src/db.ts:42-46).
Linking: eleven gates before a yes
An environment joins an account by presenting a link-proof JWT of type
RELAY_LINK_PROOF_TYP from the shared JWT helpers (infra/relay/src/environments/EnvironmentLinker.ts:6-11). The relay never trusts it wholesale; it walks a staged validation chain, and any
failed stage is reported by name in the error (infra/relay/src/environments/EnvironmentLinker.ts:38-63):
The relay checks each requested capability: requesting managed tunnels fails unless
the proof carries the managed_tunnels scope, and
notifications or Live Activities require
agent_activity_notifications (infra/relay/src/environments/EnvironmentLinker.ts:95-107). The endpoint gate requires https for the HTTP base URL and wss for the WebSocket
base URL, refusing anything else (infra/relay/src/environments/EnvironmentLinker.ts:109-117).
Only then does the relay upsert the link row, mint an environment credential, and
provision the managed endpoint, returning all three results to the caller (infra/relay/src/environments/EnvironmentLinker.ts:73-91).
Endpoints from a digest
A managed endpoint is a Cloudflare tunnel with a hostname the relay derives, not one
anyone picks. The same environment under the same account always maps to the same
name, and production hostnames read
prod-<digest>.<RELAY_TUNNEL_ZONE_NAME> while
personal Alchemy stages prefix their own sanitized stage name (infra/relay/README.md:102-108).
stage:userId:environmentId, hashed into the
hostname suffix (infra/relay/src/deploymentConfig.ts:88-99).
https://<hostname>/ and
wss://<hostname>/ws with
providerKind: "cloudflare_tunnel" (infra/relay/src/deploymentConfig.ts:112-118).
infra/relay/src/environments/ManagedEndpointProvider.ts:77-84).
infra/relay/src/environments/EnvironmentConnector.ts:45-69).
ManagedEndpointProvider.ts:57-75).
Provisioning stage names verbatim from
infra/relay/src/environments/ManagedEndpointProvider.ts:43-55.
Two issuers, zero interchange
Environment server and relay keep separate credentials, issuers, and trust boundaries,
deliberately shaped like OAuth so permission checks can be audited against established
concepts (docs/internals/environment-auth.md:5-7). The token
endpoint profiles RFC 8693 token-exchange vocabulary:
subject_token,
requested_token_type,
issued_token_type (docs/internals/environment-auth.md:113-119). It is not a general authorization server; the bootstrap token type is private and
errors are typed (docs/internals/environment-auth.md:123-127).
Authorization is capability-based. Ordinary pairing links grant exactly four
client-operation scopes plus read access to relay connectivity; bootstrap credentials
add the three administrative ones (docs/internals/environment-auth.md:25-29). A WebSocket ticket carries its session's scopes, and every RPC method re-checks
the one it needs:
"Creating a ticket is not authorization to call every RPC method" (docs/internals/environment-auth.md:105-111).
The scope model was born in a hard cutover. Migration
031_AuthAuthorizationScopes deleted existing pairing links
and sessions rather than silently mapping old role-bearing records to new
capabilities; upgraded clients simply pair again (docs/internals/environment-auth.md:130-135).
Capability scopes
The eight environment-side scopes (docs/internals/environment-auth.md:14-23). Pairing grants the first four plus relay:read;
bootstrap adds access:read access:write relay:write.
The boundary rule
"An environment access token is not a relay token and cannot be presented to the
relay."
(docs/internals/environment-auth.md:139-141). Different
issuers, different keys, different trust: a credential from one side is inert paper
on the other.
Production shape: re-verify, do not assume
Repository code is not the complete source of truth for production. The working rule
names three layers that must agree before any diagnosis or change: the repository
(infra/relay and its docs), the host running the Postgres
origin and cloudflared, and the live Cloudflare account with
its Workers, DNS, Access, tunnels, Hyperdrive, and queues (AGENTS.md, Relay and
production infrastructure).
The documented shape is a starting point that must be verified. The fork's default
relay URL https://relay.athelstan.xyz and hosted app
https://app.athelstan.xyz are fork-patched constants with
comments warning "An upstream sync will try to revert them" and directing the
reader to keep the fork's values, guarded by tests in both the web and server packages
(packages/shared/src/connectAuth.ts:12-32;
apps/web/src/cloud/publicConfig.test.ts:15-21;
apps/server/src/cloud/publicConfig.test.ts:38-46).
Docs can lag, and this area has an example on file. The relay README's deployment
section still says the prod stage
"owns the retained PlanetScale database" (infra/relay/README.md:92), while the schema is Drizzle postgres with migrations under
infra/relay/migrations/postgres (infra/relay/src/db.ts:42-46) and operations document a self-hosted Postgres origin behind Access. Treat
repository prose as one witness among three.
Repository
infra/relay for the Alchemy stack, Worker, migrations, and
contracts; docs/internals and
docs/operations for design and procedure. The first
witness, read-only.
The host
The origin machine runs the Postgres container and
cloudflared; the database is bound to loopback, not the
public network (AGENTS.md). Inspect before touching; never restart casually.
Cloudflare
Workers, custom domains, DNS, Access, tunnels, Hyperdrive, queues, and logs, discovered through the Cloudflare API and Wrangler. Physical Worker names are generated and may change (AGENTS.md).
Rules of engagement
- Never print secrets: .env files, tunnel tokens, service tokens, database passwords, signer keys, Worker secrets (AGENTS.md).
-
Inspect read-only first: repository, then host, then Cloudflare. Start with the
provisioned Axiom view or APL queries instead of tailing the Worker (
docs/operations/relay-observability.md:50-53). - Do not deploy, migrate, rotate credentials, edit DNS or Access, or restart services unless the request is explicit (AGENTS.md).
- Production truth lives across three layers. Any single layer, including this page, is a claim.
Go deeper
Primary sources for this page. Read them in the repository, not in a summary, before changing any of this behavior.
Relay plane
infra/relay/README.mdinfra/relay/alchemy.run.tsinfra/relay/src/worker.tsinfra/relay/src/db.tsinfra/relay/src/http/Api.tsinfra/relay/src/auth/RelayTokens.ts
Environments and docs
infra/relay/src/environments/EnvironmentLinker.ts-
infra/relay/src/environments/EnvironmentCredentials.ts -
infra/relay/src/environments/EnvironmentConnector.ts -
infra/relay/src/environments/ManagedEndpointProvider.ts docs/internals/environment-auth.mddocs/operations/relay-observability.md
Continue the atlas
The Signer & authority page covers the local, deterministic key handling on the environment side of this boundary. Contracts holds the wire shapes the relay and its clients share, and Clients shows how web and desktop consume endpoints and tickets.