From 4bd5bcfa380941c5a7102b169e6195e744c97bb3 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Wed, 22 Jul 2026 13:01:24 -0400 Subject: [PATCH] fix(server): bound editor discovery during config loading --- apps/server/src/server.test.ts | 18 ++++++++++++++++++ apps/server/src/ws.ts | 13 ++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index c8c4ff377e8..5da09d814f9 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -73,6 +73,7 @@ const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); import * as ServerConfig from "./config.ts"; import { makeRoutesLayer } from "./server.ts"; +import { resolveAvailableEditorsForConfig } from "./ws.ts"; import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as GitManager from "./git/GitManager.ts"; import * as Keybindings from "./keybindings.ts"; @@ -3728,6 +3729,23 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("does not block server config when editor discovery never resolves", () => + Effect.gen(function* () { + const discoveryInterrupted = yield* Deferred.make(); + const responseFiber = yield* resolveAvailableEditorsForConfig( + Effect.never.pipe( + Effect.onInterrupt(() => Deferred.succeed(discoveryInterrupted, undefined)), + ), + ).pipe(Effect.forkChild); + + yield* TestClock.adjust(Duration.seconds(5)); + + const availableEditors = yield* Fiber.join(responseFiber); + yield* Deferred.await(discoveryInterrupted); + assert.deepEqual(availableEditors, []); + }), + ); + it.effect( "rejects websocket rpc handshake when a session token is only provided via query string", () => diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 08b1770a0a2..129b5b2b2a6 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -118,6 +118,15 @@ import * as RelayClient from "@t3tools/shared/relayClient"; const isOrchestrationDispatchCommandError = Schema.is(OrchestrationDispatchCommandError); const nowIso = Effect.map(DateTime.now, DateTime.formatIso); +const EDITOR_DISCOVERY_TIMEOUT = Duration.seconds(5); + +export const resolveAvailableEditorsForConfig = ( + discovery: Effect.Effect, E, R>, +) => + discovery.pipe( + Effect.timeoutOption(EDITOR_DISCOVERY_TIMEOUT), + Effect.map(Option.getOrElse(() => [])), + ); function unexpectedCompatibilityError(error: never): never { throw new Error(`Unhandled compatibility error: ${String(error)}`); @@ -1080,7 +1089,9 @@ const makeWsRpcLayer = ( keybindings: keybindingsConfig.keybindings, issues: keybindingsConfig.issues, providers, - availableEditors: yield* externalLauncher.resolveAvailableEditors(), + availableEditors: yield* resolveAvailableEditorsForConfig( + externalLauncher.resolveAvailableEditors(), + ), observability: { logsDirectoryPath: config.logsDir, localTracingEnabled: true,