diff --git a/apps/server/src/project/AgentSessionScanner.test.ts b/apps/server/src/project/AgentSessionScanner.test.ts index ce1830b8c5710..0844e3cd71834 100644 --- a/apps/server/src/project/AgentSessionScanner.test.ts +++ b/apps/server/src/project/AgentSessionScanner.test.ts @@ -22,11 +22,13 @@ import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; +import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import * as ServerConfig from "../config.ts"; +import * as ProcessRunner from "../processRunner.ts"; import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as AgentSessionScanner from "./AgentSessionScanner.ts"; @@ -56,10 +58,16 @@ interface ScannerTestInput { readonly importedWorkspaceRoots?: ReadonlyArray; readonly configBaseDir?: string; readonly providerInstances?: ContractServerSettings["providerInstances"]; + readonly processRunner?: Layer.Layer; } const layerScannerTest = (input: ScannerTestInput) => - AgentSessionScanner.layer.pipe( + (input.processRunner === undefined + ? AgentSessionScanner.layer + : Layer.effect(AgentSessionScanner.AgentSessionScanner, AgentSessionScanner.make).pipe( + Layer.provide(input.processRunner), + ) + ).pipe( Layer.provide( Layer.mergeAll( ServerSettings.layerTest({ @@ -1959,11 +1967,38 @@ describe.skipIf(HostProcess.Platform.defaultValue() === "win32")( }); } + // The deadline fires for every read still in flight, so on a slow runner the + // neighbours' `cat` children must have exited before the clock moves. + const neighbourConfigs = new Set( + [repos[0], repos[2]].map((repo) => path.join(repo, ".git", "config")), + ); + const finishedNeighbourReads = yield* Queue.unbounded(); + const processRunner = Layer.effect( + ProcessRunner.ProcessRunner, + Effect.gen(function* () { + const runner = yield* ProcessRunner.ProcessRunner; + return ProcessRunner.ProcessRunner.of({ + run: (input) => { + const filePath = input.args.at(-1) ?? ""; + return neighbourConfigs.has(filePath) + ? runner + .run(input) + .pipe(Effect.ensuring(Queue.offer(finishedNeighbourReads, filePath))) + : runner.run(input); + }, + }); + }), + ).pipe(Layer.provide(ProcessRunner.layer), Layer.provide(NodeServices.layer)); + for (let attempt = 1; attempt <= 2; attempt++) { - const scan = yield* Effect.forkChild(runScan({ claudeHomePath, codexHomePath })); + const scan = yield* Effect.forkChild( + runScan({ claudeHomePath, codexHomePath, processRunner }), + ); const result = yield* Effect.scoped( Effect.gen(function* () { yield* awaitGateReader(path.join(repos[1], ".git", "config")); + yield* Queue.take(finishedNeighbourReads); + yield* Queue.take(finishedNeighbourReads); yield* TestClock.adjust("2 seconds"); return yield* Fiber.join(scan); }),