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* () {...@@ -317,12 +317,36 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () {
317 }317 }
318 disposed = true;318 disposed = true;
319319
320 yield* Effect.promise(() => runtime.runPromise(providerService.stopAll()));320 const shutdown = Effect.gen(function* () {
321 yield* Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void)));321 const stopAllExit = yield* Effect.exit(
322 yield* Effect.promise(() => runtime.dispose());322 Effect.promise(() => runtime.runPromise(providerService.stopAll())),
323 yield* Effect.sync(() => {323 );
324 fs.rmSync(rootDir, { recursive: true, force: true });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 }
325 });341 });
342
343 yield* shutdown.pipe(
344 Effect.ensuring(
345 Effect.sync(() => {
346 fs.rmSync(rootDir, { recursive: true, force: true });
347 }),
348 ),
349 );
326 });350 });
327351
328 return {352 return {
apps/server/integration/orchestrationEngine.integration.test.ts+19-1
...@@ -13,7 +13,7 @@ import {...@@ -13,7 +13,7 @@ import {
13 ThreadId,13 ThreadId,
14} from "@t3tools/contracts";14} from "@t3tools/contracts";
15import { assert, it } from "@effect/vitest";15import { assert, it } from "@effect/vitest";
16import { Effect, Option } from "effect";16import { Effect, Exit, Option } from "effect";
1717
18import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts";18import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts";
19import {19import {
...@@ -133,6 +133,24 @@ const startTurn = (input: {...@@ -133,6 +133,24 @@ const startTurn = (input: {
133 createdAt: nowIso(),133 createdAt: nowIso(),
134 });134 });
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
136it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () =>154it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () =>
137 withHarness((harness) =>155 withHarness((harness) =>
138 Effect.gen(function* () {156 Effect.gen(function* () {