diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index e28839cd05391b574d563daff3cc89b91b5f2e27..1cb0e80705e16ee3a4ef14ee2a2c04dc75c4f2fb 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -317,12 +317,36 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () { } disposed = true; - yield* Effect.promise(() => runtime.runPromise(providerService.stopAll())); - yield* Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void))); - yield* Effect.promise(() => runtime.dispose()); - yield* Effect.sync(() => { - fs.rmSync(rootDir, { recursive: true, force: true }); + const shutdown = Effect.gen(function* () { + const stopAllExit = yield* Effect.exit( + Effect.promise(() => runtime.runPromise(providerService.stopAll())), + ); + const closeScopeExit = yield* Effect.exit( + Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void))), + ); + const disposeRuntimeExit = yield* Effect.exit(Effect.promise(() => runtime.dispose())); + + const failureCause = + Exit.isFailure(stopAllExit) + ? stopAllExit.cause + : Exit.isFailure(closeScopeExit) + ? closeScopeExit.cause + : Exit.isFailure(disposeRuntimeExit) + ? disposeRuntimeExit.cause + : null; + + if (failureCause) { + yield* Effect.failCause(failureCause); + } }); + + yield* shutdown.pipe( + Effect.ensuring( + Effect.sync(() => { + fs.rmSync(rootDir, { recursive: true, force: true }); + }), + ), + ); }); return { diff --git a/apps/server/integration/orchestrationEngine.integration.test.ts b/apps/server/integration/orchestrationEngine.integration.test.ts index 1913beaa3302314aef9aa6c430eae4230e9b2786..010be960a2fd1464f4be1626c1258f83eddaf356 100644 --- a/apps/server/integration/orchestrationEngine.integration.test.ts +++ b/apps/server/integration/orchestrationEngine.integration.test.ts @@ -13,7 +13,7 @@ import { ThreadId, } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Option } from "effect"; +import { Effect, Exit, Option } from "effect"; import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; import { @@ -133,6 +133,24 @@ const startTurn = (input: { createdAt: nowIso(), }); +it.live("always removes integration temp dirs when shutdown fails", () => + Effect.gen(function* () { + const harness = yield* makeOrchestrationIntegrationHarness; + const rootDir = harness.rootDir; + const originalStopAll = harness.providerService.stopAll.bind(harness.providerService); + + ( + harness.providerService as unknown as { + stopAll: typeof harness.providerService.stopAll; + } + ).stopAll = () => originalStopAll().pipe(Effect.andThen(Effect.fail(new Error("boom")))); + + const disposeExit = yield* Effect.exit(harness.dispose); + assert.equal(Exit.isFailure(disposeExit), true); + assert.equal(fs.existsSync(rootDir), false); + }), +); + it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () => withHarness((harness) => Effect.gen(function* () {