Skip to content

Commit 68e50db

Browse files
refactor(server): CloudLink fails with its own errors; the connect routes map them to HTTP (#16341)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent ff873b2 commit 68e50db

8 files changed

Lines changed: 577 additions & 337 deletions

File tree

‎apps/server/src/cloud/CloudLink.lifecycle.test.ts‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import {
4545
managedTunnelStartupAction,
4646
retryManagedTunnelRegistration,
4747
} from "./managedTunnelStartup.ts";
48-
import { shouldRetryCloudLink } from "./relayResponse.ts";
4948
import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
5049

5150
const unusedSecretStoreOperation = () => Effect.die("unused secret-store operation");
@@ -109,10 +108,7 @@ describe("reconcileDesiredCloudLink", () => {
109108
const link = yield* CloudLink.CloudLink;
110109
const error = yield* Effect.flip(link.reconcileDesiredLink("http://127.0.0.1:3774"));
111110

112-
expect(error).toMatchObject({
113-
_tag: "EnvironmentHttpUnauthorizedError",
114-
message: "Run `t3 connect link` to authorize this environment.",
115-
});
111+
expect(error._tag).toBe("CloudLinkAuthorizationMissingError");
116112
}).pipe(
117113
Effect.provide(CloudLink.layer),
118114
Effect.provideService(HttpServer.HttpServer, idleHttpServer),
@@ -689,7 +685,7 @@ describe("releaseManagedTunnelOnShutdown", () => {
689685
link
690686
.registerManagedTunnelRecovery(localOrigin)
691687
.pipe(Effect.tapError(() => Deferred.succeed(firstFailure, undefined))),
692-
shouldRetryCloudLink,
688+
CloudLink.shouldRetryCloudLink,
693689
link
694690
.startManagedTunnelIfOriginConfirmed(localOrigin, {
695691
requireConfirmedOrigin: false,
@@ -1051,10 +1047,10 @@ describe("releaseManagedTunnelOnShutdown", () => {
10511047
});
10521048

10531049
it.effect.each([
1054-
{ status: 401, errorTag: "EnvironmentHttpUnauthorizedError" },
1055-
{ status: 403, errorTag: "EnvironmentHttpForbiddenError" },
1056-
{ status: 409, errorTag: "EnvironmentHttpBadRequestError" },
1057-
])("preserves a permanent $status relay recovery failure", ({ status, errorTag }) => {
1050+
{ status: 401, rejection: "unauthorized" },
1051+
{ status: 403, rejection: "forbidden" },
1052+
{ status: 409, rejection: "rejected" },
1053+
])("preserves a permanent $status relay recovery failure", ({ status, rejection }) => {
10581054
const { store } = makeMemorySecretStore([
10591055
[CLOUD_ENDPOINT_RUNTIME_CONFIG, "old-config"],
10601056
[RELAY_URL_SECRET, "https://relay.example.test"],
@@ -1068,7 +1064,8 @@ describe("releaseManagedTunnelOnShutdown", () => {
10681064
const link = yield* CloudLink.CloudLink;
10691065
const error = yield* Effect.flip(link.recoverManagedTunnel("http://127.0.0.1:3773"));
10701066

1071-
expect(error._tag).toBe(errorTag);
1067+
expect(error).toMatchObject({ _tag: "RelayRequestError", rejection });
1068+
expect(CloudLink.shouldRetryCloudLink(error)).toBe(false);
10721069
expect(requests).toHaveLength(1);
10731070
expect(applyConfigCalls).toEqual([]);
10741071
}).pipe(

‎apps/server/src/cloud/CloudLink.test.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ it.effect("puts the relay back when the local save fails", () =>
137137
const error = yield* preferences
138138
.update({ publishAgentActivity: true, holdWebhooksWhileOffline: true })
139139
.pipe(Effect.flip);
140-
assert.equal(error._tag, "EnvironmentHttpInternalServerError");
140+
assert.deepInclude(error, {
141+
_tag: "CloudLinkInternalError",
142+
operation: "persist-preferences",
143+
});
141144
assert.deepEqual(relayCalls, [true, false]);
142145
assert.equal(
143146
new TextDecoder().decode(stored.get(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET)),
@@ -153,7 +156,10 @@ it.effect("leaves the relay untouched when the activity setting can't be saved",
153156
const error = yield* preferences
154157
.update({ publishAgentActivity: true, holdWebhooksWhileOffline: true })
155158
.pipe(Effect.flip);
156-
assert.equal(error._tag, "EnvironmentHttpInternalServerError");
159+
assert.deepInclude(error, {
160+
_tag: "CloudLinkInternalError",
161+
operation: "persist-preferences",
162+
});
157163
assert.deepEqual(relayCalls, []);
158164
assert.equal(
159165
new TextDecoder().decode(stored.get(HOLD_WEBHOOKS_WHILE_OFFLINE_SECRET)),
@@ -184,7 +190,7 @@ it.effect("changes nothing when the current activity setting can't be read", ()
184190
const error = yield* preferences
185191
.update({ publishAgentActivity: true, holdWebhooksWhileOffline: true })
186192
.pipe(Effect.flip);
187-
assert.equal(error._tag, "EnvironmentHttpInternalServerError");
193+
assert.deepInclude(error, { _tag: "CloudLinkInternalError", operation: "read-preferences" });
188194
assert.deepEqual(relayCalls, []);
189195
assert.equal(new TextDecoder().decode(stored.get(PUBLISH_AGENT_ACTIVITY_SECRET)), "false");
190196
}),
@@ -231,7 +237,7 @@ it.effect("changes nothing when the current hold setting can't be read", () =>
231237
const error = yield* preferences
232238
.update({ publishAgentActivity: true, holdWebhooksWhileOffline: false })
233239
.pipe(Effect.flip);
234-
assert.equal(error._tag, "EnvironmentHttpInternalServerError");
240+
assert.deepInclude(error, { _tag: "CloudLinkInternalError", operation: "read-preferences" });
235241
assert.deepEqual(relayCalls, []);
236242
assert.equal(new TextDecoder().decode(stored.get(PUBLISH_AGENT_ACTIVITY_SECRET)), "false");
237243
}),

0 commit comments

Comments
 (0)