authorgravatar for cjnwob@gmail.comChukwudi Nwobodo <cjnwob@gmail.com> 2026-03-08 06:22:26+00:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 14:02:29-07:00
log6593645c8092696403f39e7d6eeebeec190385dc
tree2b228810c69cf22bbc1f43c2c97d88ad9421abd8
parent296eb82c312b3c36237b51870b9a87249f5c6d83

perf: run provider health checks in background, notify clients on ready

The ProviderHealth layer blocked server startup with two sequential CLI spawns (codex --version + codex login status), each with a 4-second timeout, delaying startup by up to 8 seconds. Run health checks in the background via Effect.runPromise so the layer resolves immediately with a placeholder status. Add an onReady callback to ProviderHealthShape so wsServer can push the resolved statuses to connected clients once checks complete, preventing early-connecting clients from showing "Checking..." indefinitely.

3 files changed, 69 insertions(+), 5 deletions(-)

apps/server/src/provider/Layers/ProviderHealth.ts+49-2
......@@ -312,9 +312,56 @@ export const checkCodexProviderStatus: Effect.Effect<
312312export const ProviderHealthLive = Layer.effect(
313313 ProviderHealth,
314314 Effect.gen(function* () {
315 const codexStatus = yield* checkCodexProviderStatus;
315 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
316 let cachedStatuses: ReadonlyArray<ServerProviderStatus> = [
317 {
318 provider: CODEX_PROVIDER,
319 status: "warning",
320 available: false,
321 authStatus: "unknown",
322 checkedAt: new Date().toISOString(),
323 message: "Checking Codex CLI availability...",
324 },
325 ];
326
327 let readyListeners: Array<(statuses: ReadonlyArray<ServerProviderStatus>) => void> = [];
328 let resolved = false;
329
330 const notifyReady = (statuses: ReadonlyArray<ServerProviderStatus>) => {
331 resolved = true;
332 cachedStatuses = statuses;
333 for (const cb of readyListeners) cb(statuses);
334 readyListeners = [];
335 };
336
337 // Run health checks in the background so they don't block server startup.
338 checkCodexProviderStatus.pipe(
339 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
340 Effect.runPromise,
341 ).then((status) => {
342 notifyReady([status]);
343 }).catch(() => {
344 notifyReady([
345 {
346 provider: CODEX_PROVIDER,
347 status: "error",
348 available: false,
349 authStatus: "unknown",
350 checkedAt: new Date().toISOString(),
351 message: "Failed to check Codex CLI status.",
352 },
353 ]);
354 });
355
316356 return {
317 getStatuses: Effect.succeed([codexStatus]),
357 getStatuses: Effect.sync(() => cachedStatuses),
358 onReady: (cb) => {
359 if (resolved) {
360 cb(cachedStatuses);
361 } else {
362 readyListeners.push(cb);
363 }
364 },
318365 } satisfies ProviderHealthShape;
319366 }),
320367);
apps/server/src/provider/Services/ProviderHealth.ts+4
......@@ -15,6 +15,10 @@ export interface ProviderHealthShape {
1515 * Read provider health statuses computed at server startup.
1616 */
1717 readonly getStatuses: Effect.Effect<ReadonlyArray<ServerProviderStatus>>;
18 /**
19 * Register a callback invoked once when background health checks complete.
20 */
21 readonly onReady: (cb: (statuses: ReadonlyArray<ServerProviderStatus>) => void) => void;
1822}
1923
2024export class ProviderHealth extends ServiceMap.Service<ProviderHealth, ProviderHealthShape>()(
apps/server/src/wsServer.ts+16-3
......@@ -268,7 +268,8 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
268268 ),
269269 );
270270
271 const providerStatuses = yield* providerHealth.getStatuses;
271 // Read provider statuses lazily so background health checks are reflected.
272 const getProviderStatuses = () => Effect.runSync(providerHealth.getStatuses);
272273
273274 const clients = yield* Ref.make(new Set<WebSocket>());
274275 const logger = createLogger("ws");
......@@ -631,11 +632,23 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
631632 channel: WS_CHANNELS.serverConfigUpdated,
632633 data: {
633634 issues: event.issues,
634 providers: providerStatuses,
635 providers: getProviderStatuses(),
635636 },
636637 }),
637638 ).pipe(Effect.forkIn(subscriptionsScope));
638639
640 // Push updated provider statuses to connected clients once background health checks finish.
641 providerHealth.onReady((statuses) => {
642 broadcastPush({
643 type: "push",
644 channel: WS_CHANNELS.serverConfigUpdated,
645 data: {
646 issues: [],
647 providers: statuses,
648 },
649 }).pipe(Effect.runPromise).catch(() => {});
650 });
651
639652 yield* Scope.provide(orchestrationReactor.start, subscriptionsScope);
640653
641654 let welcomeBootstrapProjectId: ProjectId | undefined;
......@@ -883,7 +896,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
883896 keybindingsConfigPath,
884897 keybindings: keybindingsConfig.keybindings,
885898 issues: keybindingsConfig.issues,
886 providers: providerStatuses,
899 providers: getProviderStatuses(),
887900 availableEditors,
888901 };
889902