authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-24 22:36:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-24 22:36:51-07:00
loga107f8a0792c3add90d398602998c55c3ebaef27
tree527b5c104b29f46b6164d1a8c68da5e943b9bb9e
parent7b84431161a64c9f9e9b57637dec23474a1b4236
signature Signed by PGP key B5690EEEBB952194

fix(clients): a preview app no longer knocks the desktop's own server offline (#13577)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

2 files changed, 98 insertions(+), 5 deletions(-)

packages/client-runtime/src/connection/layer.ts+7
......@@ -32,11 +32,18 @@ export const watchDiscoveredCompatibility = Effect.fn("connection.watchDiscovere
3232 if (!current.environments.has(environmentId)) seenChecks.delete(environmentId);
3333 }
3434 }
35 const registered = yield* SubscriptionRef.get(registry.entries);
3536 for (const entry of current.environments.values()) {
3637 const status = Option.getOrNull(entry.status);
3738 const descriptor = status?.descriptor;
3839 if (status === null || descriptor === undefined) continue;
3940 const environmentId = entry.environment.environmentId;
41 // Discovery describes the server behind the relay route. A direct
42 // connection (the desktop's own server, a saved URL, SSH) can reach
43 // a different server with the same environment id, such as a
44 // preview app that shares the home directory. Its socket handshake
45 // already checks the protocol.
46 if (registered.get(environmentId)?.target._tag !== "RelayConnectionTarget") continue;
4047 const previous = seenChecks.get(environmentId);
4148 const fresh =
4249 previous?.checkedAt !== status.checkedAt ||
packages/client-runtime/src/connection/registry.test.ts+91-5
......@@ -683,7 +683,7 @@ describe("EnvironmentRegistry", () => {
683683
684684 it.effect("only a fresh health check for the rejected environment unlocks it", () =>
685685 Effect.gen(function* () {
686 const harness = yield* makeHarness([RELAY_TARGET], [], [], {
686 const harness = yield* makeHarness([RELAY_TARGET, SECOND_RELAY_TARGET], [], [], {
687687 initialDisabled: [RELAY_TARGET.environmentId],
688688 });
689689 const descriptor = (environmentId: EnvironmentId): ExecutionEnvironmentDescriptor => ({
......@@ -778,8 +778,8 @@ describe("EnvironmentRegistry", () => {
778778 yield* SubscriptionRef.update(discoveryState, (state) => ({
779779 ...state,
780780 environments: new Map(state.environments).set(
781 SECOND_TARGET.environmentId,
782 discovered(descriptor(SECOND_TARGET.environmentId)),
781 SECOND_RELAY_TARGET.environmentId,
782 discovered(descriptor(SECOND_RELAY_TARGET.environmentId)),
783783 ),
784784 }));
785785 yield* Deferred.await(unrelated);
......@@ -798,8 +798,8 @@ describe("EnvironmentRegistry", () => {
798798 environments: new Map([
799799 [RELAY_TARGET.environmentId, discovered(descriptor(RELAY_TARGET.environmentId))],
800800 [
801 SECOND_TARGET.environmentId,
802 discovered(descriptor(SECOND_TARGET.environmentId), "2026-09-15T00:01:00Z"),
801 SECOND_RELAY_TARGET.environmentId,
802 discovered(descriptor(SECOND_RELAY_TARGET.environmentId), "2026-09-15T00:01:00Z"),
803803 ],
804804 ]),
805805 }));
......@@ -827,6 +827,92 @@ describe("EnvironmentRegistry", () => {
827827 }),
828828 );
829829
830 it.effect("discovery leaves direct connections that share an environment id alone", () =>
831 Effect.gen(function* () {
832 const harness = yield* makeHarness([TARGET, RELAY_TARGET]);
833 const discovered = (environmentId: EnvironmentId) => {
834 const endpoint = {
835 httpBaseUrl: "https://relay.example.test",
836 wsBaseUrl: "wss://relay.example.test",
837 providerKind: "manual" as const,
838 };
839 return {
840 environment: {
841 environmentId,
842 label: "Preview server",
843 endpoint,
844 linkedAt: "2026-09-25T00:00:00Z",
845 },
846 availability: "online" as const,
847 status: Option.some<RelayEnvironmentStatusResponse>({
848 environmentId,
849 endpoint,
850 status: "online",
851 checkedAt: "2026-09-25T00:00:00Z",
852 descriptor: {
853 environmentId,
854 label: "Preview server",
855 platform: { os: "darwin", arch: "arm64" },
856 serverVersion: "2.0.0",
857 orchestrationProtocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1,
858 capabilities: { repositoryIdentity: true },
859 },
860 }),
861 error: Option.none(),
862 };
863 };
864 yield* Effect.gen(function* () {
865 const registry = yield* EnvironmentRegistry.EnvironmentRegistry;
866 yield* registry.start;
867 yield* awaitConnectionState(
868 registry,
869 TARGET.environmentId,
870 (state) => state.phase === "connected",
871 );
872 const relayChecked = yield* Deferred.make<void>();
873 yield* watchDiscoveredCompatibility().pipe(
874 Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, {
875 ...registry,
876 setCompatibility: (environmentId, error) =>
877 registry
878 .setCompatibility(environmentId, error)
879 .pipe(
880 Effect.andThen(
881 environmentId === RELAY_TARGET.environmentId
882 ? Deferred.succeed(relayChecked, undefined)
883 : Effect.void,
884 ),
885 ),
886 }),
887 Effect.provideService(
888 RelayEnvironmentDiscovery.RelayEnvironmentDiscovery,
889 RelayEnvironmentDiscovery.RelayEnvironmentDiscovery.of({
890 state:
891 yield* SubscriptionRef.make<RelayEnvironmentDiscovery.RelayEnvironmentDiscoveryState>(
892 {
893 ...RelayEnvironmentDiscovery.EMPTY_RELAY_ENVIRONMENT_DISCOVERY_STATE,
894 environments: new Map([
895 [TARGET.environmentId, discovered(TARGET.environmentId)],
896 [RELAY_TARGET.environmentId, discovered(RELAY_TARGET.environmentId)],
897 ]),
898 },
899 ),
900 refresh: Effect.void,
901 }),
902 ),
903 Effect.forkScoped,
904 );
905 yield* Deferred.await(relayChecked);
906
907 const entries = yield* SubscriptionRef.get(registry.entries);
908 expect(entries.get(RELAY_TARGET.environmentId)).toMatchObject({ enabled: false });
909 expect(entries.get(TARGET.environmentId)).toMatchObject({ enabled: true });
910 expect(entries.get(TARGET.environmentId)?.unsupportedReason).toBeUndefined();
911 expect((yield* registry.state(TARGET.environmentId)).phase).toBe("connected");
912 }).pipe(Effect.provide(harness.layer), Effect.scoped);
913 }),
914 );
915
830916 it.effect("discovery keeps unsupported environments off until compatibility changes", () =>
831917 Effect.gen(function* () {
832918 const harness = yield* makeHarness([RELAY_TARGET], [], [], {