authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-07-27 15:18:55-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-27 21:18:55+02:00
log6e3b73884bf40eeb2859cb16f1200c8e2f9cf7c3
tree95283e1b2a8ebe78cc3548429fa7228ee38c4c4f
parent5ba6ef7f84a5586e3795264cae348404dd408cb5
signature Signed by PGP key B5690EEEBB952194

fix(server): bound editor discovery during config loading (#4291)


2 files changed, 30 insertions(+), 1 deletions(-)

apps/server/src/server.test.ts+18
...@@ -73,6 +73,7 @@ const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");...@@ -73,6 +73,7 @@ const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");
7373
74import * as ServerConfig from "./config.ts";74import * as ServerConfig from "./config.ts";
75import { makeRoutesLayer } from "./server.ts";75import { makeRoutesLayer } from "./server.ts";
76import { resolveAvailableEditorsForConfig } from "./ws.ts";
76import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";77import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";
77import * as GitManager from "./git/GitManager.ts";78import * as GitManager from "./git/GitManager.ts";
78import * as Keybindings from "./keybindings.ts";79import * as Keybindings from "./keybindings.ts";
...@@ -3763,6 +3764,23 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -3763,6 +3764,23 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
3763 }).pipe(Effect.provide(NodeHttpServer.layerTest)),3764 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
3764 );3765 );
37653766
3767 it.effect("does not block server config when editor discovery never resolves", () =>
3768 Effect.gen(function* () {
3769 const discoveryInterrupted = yield* Deferred.make<void>();
3770 const responseFiber = yield* resolveAvailableEditorsForConfig(
3771 Effect.never.pipe(
3772 Effect.onInterrupt(() => Deferred.succeed(discoveryInterrupted, undefined)),
3773 ),
3774 ).pipe(Effect.forkChild);
3775
3776 yield* TestClock.adjust(Duration.seconds(5));
3777
3778 const availableEditors = yield* Fiber.join(responseFiber);
3779 yield* Deferred.await(discoveryInterrupted);
3780 assert.deepEqual(availableEditors, []);
3781 }),
3782 );
3783
3766 it.effect(3784 it.effect(
3767 "rejects websocket rpc handshake when a session token is only provided via query string",3785 "rejects websocket rpc handshake when a session token is only provided via query string",
3768 () =>3786 () =>
apps/server/src/ws.ts+12-1
...@@ -123,6 +123,15 @@ import * as RelayClient from "@t3tools/shared/relayClient";...@@ -123,6 +123,15 @@ import * as RelayClient from "@t3tools/shared/relayClient";
123const isOrchestrationDispatchCommandError = Schema.is(OrchestrationDispatchCommandError);123const isOrchestrationDispatchCommandError = Schema.is(OrchestrationDispatchCommandError);
124124
125const nowIso = Effect.map(DateTime.now, DateTime.formatIso);125const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
126const EDITOR_DISCOVERY_TIMEOUT = Duration.seconds(5);
127
128export const resolveAvailableEditorsForConfig = <A, E, R>(
129 discovery: Effect.Effect<ReadonlyArray<A>, E, R>,
130) =>
131 discovery.pipe(
132 Effect.timeoutOption(EDITOR_DISCOVERY_TIMEOUT),
133 Effect.map(Option.getOrElse(() => [])),
134 );
126135
127function unexpectedCompatibilityError(error: never): never {136function unexpectedCompatibilityError(error: never): never {
128 throw new Error(`Unhandled compatibility error: ${String(error)}`);137 throw new Error(`Unhandled compatibility error: ${String(error)}`);
...@@ -1087,7 +1096,9 @@ const makeWsRpcLayer = (...@@ -1087,7 +1096,9 @@ const makeWsRpcLayer = (
1087 keybindings: keybindingsConfig.keybindings,1096 keybindings: keybindingsConfig.keybindings,
1088 issues: keybindingsConfig.issues,1097 issues: keybindingsConfig.issues,
1089 providers,1098 providers,
1090 availableEditors: yield* externalLauncher.resolveAvailableEditors(),1099 availableEditors: yield* resolveAvailableEditorsForConfig(
1100 externalLauncher.resolveAvailableEditors(),
1101 ),
1091 observability: {1102 observability: {
1092 logsDirectoryPath: config.logsDir,1103 logsDirectoryPath: config.logsDir,
1093 localTracingEnabled: true,1104 localTracingEnabled: true,