authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-26 18:28:32-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 18:40:23-08:00
log7722cb6d2e44e0aeb136a86bc2165f1cc733146a
tree9f7c5c665666f31078ecf37a24eea790ec746b8d
parentc9a59a26bb85902b9b2e828e1f99cc0760105ae4

Ensure harness cleanup runs even when shutdown fails

- make integration harness dispose path always remove temp root dirs via `Effect.ensuring` - preserve shutdown failure by surfacing the first teardown error cause - add integration test that forces `providerService.stopAll` failure and verifies temp dir cleanup

2 files changed, 48 insertions(+), 6 deletions(-)

apps/server/integration/OrchestrationEngineHarness.integration.ts+29-5
......@@ -317,12 +317,36 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () {
317317 }
318318 disposed = true;
319319
320 yield* Effect.promise(() => runtime.runPromise(providerService.stopAll()));
321 yield* Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void)));
322 yield* Effect.promise(() => runtime.dispose());
323 yield* Effect.sync(() => {
324 fs.rmSync(rootDir, { recursive: true, force: true });
320 const shutdown = Effect.gen(function* () {
321 const stopAllExit = yield* Effect.exit(
322 Effect.promise(() => runtime.runPromise(providerService.stopAll())),
323 );
324 const closeScopeExit = yield* Effect.exit(
325 Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void))),
326 );
327 const disposeRuntimeExit = yield* Effect.exit(Effect.promise(() => runtime.dispose()));
328
329 const failureCause =
330 Exit.isFailure(stopAllExit)
331 ? stopAllExit.cause
332 : Exit.isFailure(closeScopeExit)
333 ? closeScopeExit.cause
334 : Exit.isFailure(disposeRuntimeExit)
335 ? disposeRuntimeExit.cause
336 : null;
337
338 if (failureCause) {
339 yield* Effect.failCause(failureCause);
340 }
325341 });
342
343 yield* shutdown.pipe(
344 Effect.ensuring(
345 Effect.sync(() => {
346 fs.rmSync(rootDir, { recursive: true, force: true });
347 }),
348 ),
349 );
326350 });
327351
328352 return {
apps/server/integration/orchestrationEngine.integration.test.ts+19-1
......@@ -13,7 +13,7 @@ import {
1313 ThreadId,
1414} from "@t3tools/contracts";
1515import { assert, it } from "@effect/vitest";
16import { Effect, Option } from "effect";
16import { Effect, Exit, Option } from "effect";
1717
1818import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts";
1919import {
......@@ -133,6 +133,24 @@ const startTurn = (input: {
133133 createdAt: nowIso(),
134134 });
135135
136it.live("always removes integration temp dirs when shutdown fails", () =>
137 Effect.gen(function* () {
138 const harness = yield* makeOrchestrationIntegrationHarness;
139 const rootDir = harness.rootDir;
140 const originalStopAll = harness.providerService.stopAll.bind(harness.providerService);
141
142 (
143 harness.providerService as unknown as {
144 stopAll: typeof harness.providerService.stopAll;
145 }
146 ).stopAll = () => originalStopAll().pipe(Effect.andThen(Effect.fail(new Error("boom"))));
147
148 const disposeExit = yield* Effect.exit(harness.dispose);
149 assert.equal(Exit.isFailure(disposeExit), true);
150 assert.equal(fs.existsSync(rootDir), false);
151 }),
152);
153
136154it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () =>
137155 withHarness((harness) =>
138156 Effect.gen(function* () {