diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index e95d8401ae32..168651518353 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -3,6 +3,7 @@ import { ProjectId, ProviderInstanceId, RunId, + RuntimeRequestId, ThreadId, type OrchestrationV2ThreadShell, } from "@t3tools/contracts"; @@ -80,6 +81,47 @@ describe("V2 storage cleanup eligibility", () => { }, ); + it.each(["idle", "completed", "interrupted", "failed", "cancelled", "rolled_back"] as const)( + "allows cleanup once its thread is %s", + (status) => { + expect(storageCleanupThreadIdle(candidateWithStatus(status), NOW_MS)).toBe(true); + }, + ); + + it.each(["completed", "interrupted", "cancelled", "rolled_back"] as const)( + "retains %s while background work is pending", + (status) => { + expect( + storageCleanupThreadIdle( + { + ...candidateWithStatus(status), + pendingBackgroundTasks: [{ taskId: "task-1", kind: "command" }], + }, + NOW_MS, + ), + ).toBe(false); + }, + ); + + it.each(["completed", "interrupted", "cancelled", "rolled_back"] as const)( + "retains %s while a runtime request is pending", + (status) => { + expect( + storageCleanupThreadIdle( + { + ...candidateWithStatus(status), + pendingRuntimeRequest: { + id: RuntimeRequestId.make("request-1"), + kind: "command", + createdAt: at(0), + }, + }, + NOW_MS, + ), + ).toBe(false); + }, + ); + it("retains an active run even if the shell status is idle", () => { expect( storageCleanupThreadIdle({ ...candidate(), activeRunId: RunId.make("run") }, NOW_MS), diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index ec7a10e403cc..ca83734af3cd 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -85,7 +85,12 @@ export function storageCleanupThreadIdle(thread: OrchestrationV2ThreadShell, now thread.branch !== null && thread.worktreePath !== null && thread.activeRunId === null && - (thread.status === "idle" || thread.status === "failed") && + (thread.status === "idle" || + thread.status === "completed" || + thread.status === "interrupted" || + thread.status === "failed" || + thread.status === "cancelled" || + thread.status === "rolled_back") && (thread.pendingBackgroundTasks?.length ?? 0) === 0 && thread.pendingRuntimeRequest === null && !threadHasQueuedTurnStart(thread, now)