From a143fa3c56be890346b8a7915a6a870529201dda Mon Sep 17 00:00:00 2001 From: shivam <91240327+shivamhwp@users.noreply.github.com> Date: Tue, 13 Oct 2026 08:12:08 +0530 Subject: [PATCH] fix(client): T3 Connect clients reconnect once a new tunnel is ready (#16554) Co-authored-by: PR Batch Tester Co-authored-by: Claude Opus 5.5 (1M context) --- .../src/connection/errors.test.ts | 33 +++++++++++++++++++ .../client-runtime/src/connection/errors.ts | 14 ++++++++ .../src/relay/errorPresentation.ts | 3 ++ 3 files changed, 50 insertions(+) diff --git a/packages/client-runtime/src/connection/errors.test.ts b/packages/client-runtime/src/connection/errors.test.ts index a892ceda7767e855f9f83d6f6335f6aeaef5fb70..465ff4cec145466617225f55d9acd3632f7f89b3 100644 --- a/packages/client-runtime/src/connection/errors.test.ts +++ b/packages/client-runtime/src/connection/errors.test.ts @@ -1,6 +1,7 @@ import { EnvironmentAuthInvalidError } from "@t3tools/contracts"; import { RelayAuthInvalidError, + RelayEnvironmentConnectNotAuthorizedError, RelayEnvironmentEndpointTimedOutError, } from "@t3tools/contracts/relay"; import { describe, expect, it } from "@effect/vitest"; @@ -35,6 +36,38 @@ describe("mapManagedRelayError", () => { }); }); + it("retries a tunnel that is still being set up, and blocks other refusals", () => { + const mapConnectRefusal = (reason: RelayEnvironmentConnectNotAuthorizedError["reason"]) => { + const relayError = new RelayEnvironmentConnectNotAuthorizedError({ + code: "environment_connect_not_authorized", + reason, + traceId: "trace-connect-refused", + }); + return mapManagedRelayError( + new ManagedRelayRequestFailedError({ + action: "connect relay environment", + cause: relayError, + relayError, + }), + ); + }; + + expect(mapConnectRefusal("managed_endpoint_allocation_not_ready")).toMatchObject({ + _tag: "ConnectionTransientError", + reason: "endpoint-unavailable", + detail: "The environment's T3 Connect tunnel is still being set up.", + traceId: "trace-connect-refused", + }); + expect(mapConnectRefusal("endpoint_provider_not_managed")).toMatchObject({ + _tag: "ConnectionBlockedError", + reason: "permission", + }); + expect(mapConnectRefusal(undefined)).toMatchObject({ + _tag: "ConnectionBlockedError", + reason: "permission", + }); + }); + it("presents clock skew as one possible cause for a generic DPoP error", () => { const mapped = mapManagedRelayError( new ManagedRelayRequestFailedError({ diff --git a/packages/client-runtime/src/connection/errors.ts b/packages/client-runtime/src/connection/errors.ts index be7a1822cbdef9ff85ea998c8da8b1775f88639b..4cdfc3eee71e4614b1c13ff3dd8aee0d44272a74 100644 --- a/packages/client-runtime/src/connection/errors.ts +++ b/packages/client-runtime/src/connection/errors.ts @@ -46,6 +46,20 @@ function relayProtectedError(error: RelayProtectedError): ConnectionAttemptError traceId: error.traceId, }); case "RelayEnvironmentConnectNotAuthorizedError": + // A host swapping in a new tunnel leaves its allocation unready until + // the tunnel is up, and nothing on the client changes when it is. + if (error.reason === "managed_endpoint_allocation_not_ready") { + return new ConnectionTransientError({ + reason: "endpoint-unavailable", + detail: relayProtectedErrorMessage(error), + traceId: error.traceId, + }); + } + return new ConnectionBlockedError({ + reason: "permission", + detail: relayProtectedErrorMessage(error), + traceId: error.traceId, + }); case "RelayEnvironmentLinkProofInvalidError": case "RelayEnvironmentLinkLimitExceededError": return new ConnectionBlockedError({ diff --git a/packages/client-runtime/src/relay/errorPresentation.ts b/packages/client-runtime/src/relay/errorPresentation.ts index 1e578cf4534507e471ad392ae8675240d5bd7d55..0a8259ef55bbb8bb10dfbee77cfa9517f94ea96a 100644 --- a/packages/client-runtime/src/relay/errorPresentation.ts +++ b/packages/client-runtime/src/relay/errorPresentation.ts @@ -43,6 +43,9 @@ export function relayProtectedErrorMessage(error: RelayProtectedError): string { if (error.reason === "environment_link_not_found") { return "Relay has no active link for this environment. The environment server may not have re-established its link yet."; } + if (error.reason === "managed_endpoint_allocation_not_ready") { + return "The environment's T3 Connect tunnel is still being set up."; + } return error.reason ? `Relay rejected the environment connection request (${error.reason}).` : "Relay rejected the environment connection request."; -- 2.54.0