From 939afacef526a0e8f9ceb47dec9bd70f52b13a45 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 24 Oct 2026 09:16:06 -0500 Subject: [PATCH 1/2] fix(muse): slow Windows starts no longer show Muse as unavailable The Muse version probe used the shared 4 second budget. On Windows, Muse can take 4-9 seconds to answer --version inside a job object, and the processes the T3 server starts run inside one, so every probe timed out. Give the probe 15 seconds; it still stops a hung CLI. Fixes #24165 Co-Authored-By: Claude Opus 5.5 --- .../provider-muse/src/server/status.test.ts | 42 +++++++++++++++++++ packages/provider-muse/src/server/status.ts | 7 +++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 04f8e1e630d08..812583f4bb01e 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -2,8 +2,13 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { MuseSettings } from "../settings.ts"; import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Schema from "effect/Schema"; +import * as Sink from "effect/Sink"; +import * as Stream from "effect/Stream"; +import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; +import * as TestClock from "effect/testing/TestClock"; import { vi } from "vite-plus/test"; import { writeFakeCli } from "@t3tools/provider-testing/fakeCli"; @@ -54,6 +59,24 @@ const fakeCli = (source = 'process.stdout.write("muse 1.0.3-R2198.1\\n");') => return writeFakeCli({ directory, name: "muse", source }); }); +const slowVersionSpawner = ChildProcessSpawner.make(() => + Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(900_000_001), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)).pipe(Effect.delay("6 seconds")), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + unref: Effect.succeed(Effect.void), + stdin: Sink.drain, + stdout: Stream.succeed(new TextEncoder().encode("muse 1.4.12-R6909.1\n")), + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + }), + ), +); + it.layer(NodeServices.layer)("Muse status", (it) => { it.effect("does not expose CLI stderr or start an SDK host after a failed version check", () => Effect.scoped( @@ -128,4 +151,23 @@ it.layer(NodeServices.layer)("Muse status", (it) => { }), ), ); + + it.effect("waits for a version check that takes several seconds to answer", () => + Effect.gen(function* () { + const host = makeHost(metaCatalog); + const probing = yield* checkMuseProviderStatus( + settings({ enabled: true, binaryPath: "muse" }), + undefined, + undefined, + async () => host, + ).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner), + Effect.forkChild({ startImmediately: true }), + ); + yield* TestClock.adjust("6 seconds"); + const snapshot = yield* Fiber.join(probing); + expect(snapshot.status).toBe("ready"); + expect(snapshot.version).toBe("1.4.12-R6909.1"); + }), + ); }); diff --git a/packages/provider-muse/src/server/status.ts b/packages/provider-muse/src/server/status.ts index bb7fcc6adecbe..7a8311b4b286f 100644 --- a/packages/provider-muse/src/server/status.ts +++ b/packages/provider-muse/src/server/status.ts @@ -13,13 +13,16 @@ import { discoverMuseModels, museModelCapabilities } from "./modelCatalog.ts"; import { buildServerProvider, COMPACT_SLASH_COMMAND, - DEFAULT_TIMEOUT_MS, isCommandMissingCause, providerModelsFromSettings, spawnAndCollect, type ProviderProbeResult, } from "@t3tools/provider-core/server/snapshotProbe"; +// Meant to stop a hung `--version`. On Windows, Muse can take 4-9 seconds to start +// inside a job object, and the processes the T3 server starts run inside one. +const MUSE_VERSION_PROBE_TIMEOUT = "15 seconds"; + const MUSE_PRESENTATION = { displayName: "Muse Code", showInteractionModeToggle: false, @@ -82,7 +85,7 @@ export const checkMuseProviderStatus = Effect.fn("checkMuseProviderStatus")(func ...(cwd ? { cwd } : {}), }), ); - }).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); + }).pipe(Effect.timeoutOption(MUSE_VERSION_PROBE_TIMEOUT), Effect.result); if (Result.isFailure(versionResult)) { const missing = isCommandMissingCause(versionResult.failure); From 11e38e53f734ae7c7854c43957747a4819fa0d29 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Mon, 26 Oct 2026 04:27:57 +0000 Subject: [PATCH 2/2] test(muse): verify slow version probes remain bounded --- .../provider-muse/src/server/status.test.ts | 58 +++++++++++++------ packages/provider-muse/src/server/status.ts | 3 +- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 812583f4bb01e..2bc5c03da9ff7 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -59,23 +59,24 @@ const fakeCli = (source = 'process.stdout.write("muse 1.0.3-R2198.1\\n");') => return writeFakeCli({ directory, name: "muse", source }); }); -const slowVersionSpawner = ChildProcessSpawner.make(() => - Effect.succeed( - ChildProcessSpawner.makeHandle({ - pid: ChildProcessSpawner.ProcessId(900_000_001), - exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)).pipe(Effect.delay("6 seconds")), - isRunning: Effect.succeed(false), - kill: () => Effect.void, - unref: Effect.succeed(Effect.void), - stdin: Sink.drain, - stdout: Stream.succeed(new TextEncoder().encode("muse 1.4.12-R6909.1\n")), - stderr: Stream.empty, - all: Stream.empty, - getInputFd: () => Sink.drain, - getOutputFd: () => Stream.empty, - }), - ), -); +const slowVersionSpawner = (delayMs: number) => + ChildProcessSpawner.make(() => + Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(900_000_001), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)).pipe(Effect.delay(delayMs)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + unref: Effect.succeed(Effect.void), + stdin: Sink.drain, + stdout: Stream.succeed(new TextEncoder().encode("muse 1.4.12-R6909.1\n")), + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + }), + ), + ); it.layer(NodeServices.layer)("Muse status", (it) => { it.effect("does not expose CLI stderr or start an SDK host after a failed version check", () => @@ -161,7 +162,7 @@ it.layer(NodeServices.layer)("Muse status", (it) => { undefined, async () => host, ).pipe( - Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner), + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner(6_000)), Effect.forkChild({ startImmediately: true }), ); yield* TestClock.adjust("6 seconds"); @@ -170,4 +171,25 @@ it.layer(NodeServices.layer)("Muse status", (it) => { expect(snapshot.version).toBe("1.4.12-R6909.1"); }), ); + + it.effect("stops a version check after fifteen seconds without starting the SDK", () => + Effect.gen(function* () { + const createHost = vi.fn(async () => makeHost(metaCatalog)); + const probing = yield* checkMuseProviderStatus( + settings({ enabled: true, binaryPath: "muse" }), + undefined, + undefined, + createHost, + ).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner(16_000)), + Effect.forkChild({ startImmediately: true }), + ); + yield* TestClock.adjust("15 seconds"); + const snapshot = yield* Fiber.join(probing); + expect(snapshot.status).toBe("error"); + expect(snapshot.message).toBe("Muse Code CLI version check timed out."); + expect(snapshot.version).toBeNull(); + expect(createHost).not.toHaveBeenCalled(); + }), + ); }); diff --git a/packages/provider-muse/src/server/status.ts b/packages/provider-muse/src/server/status.ts index 86fcdc277e576..9fd25c7b7874c 100644 --- a/packages/provider-muse/src/server/status.ts +++ b/packages/provider-muse/src/server/status.ts @@ -19,8 +19,7 @@ import { type ProviderProbeResult, } from "@t3tools/provider-core/server/snapshotProbe"; -// Meant to stop a hung `--version`. On Windows, Muse can take 4-9 seconds to start -// inside a job object, and the processes the T3 server starts run inside one. +// Muse can take 4-9 seconds to start inside a Windows job object. const MUSE_VERSION_PROBE_TIMEOUT = "15 seconds"; const MUSE_PRESENTATION = {