authorgravatar for anshsinghapa@gmail.comANSH SINGH <anshsinghapa@gmail.com> 2026-10-07 22:55:05+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-07 10:25:05-07:00
loge0201d3ab51b4593eff3c41d03ab9c9b2dc07471
tree3490b9732cc0cd28595802eb78ff12f3864e54a0
parent93e864042cf066f4cdb3efe65dbefaaf0d168894
signature Signed by PGP key B5690EEEBB952194

fix(server): free worktrees for terminal thread statuses (#15150)


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

apps/server/src/storageCleanup.test.ts+42
......@@ -3,6 +3,7 @@ import {
33 ProjectId,
44 ProviderInstanceId,
55 RunId,
6 RuntimeRequestId,
67 ThreadId,
78 type OrchestrationV2ThreadShell,
89} from "@t3tools/contracts";
......@@ -84,6 +85,47 @@ describe("V2 storage cleanup eligibility", () => {
8485 },
8586 );
8687
88 it.each(["idle", "completed", "interrupted", "failed", "cancelled", "rolled_back"] as const)(
89 "allows cleanup once its thread is %s",
90 (status) => {
91 expect(storageCleanupThreadIdle(candidateWithStatus(status), NOW_MS)).toBe(true);
92 },
93 );
94
95 it.each(["completed", "interrupted", "cancelled", "rolled_back"] as const)(
96 "retains %s while background work is pending",
97 (status) => {
98 expect(
99 storageCleanupThreadIdle(
100 {
101 ...candidateWithStatus(status),
102 pendingBackgroundTasks: [{ taskId: "task-1", kind: "command" }],
103 },
104 NOW_MS,
105 ),
106 ).toBe(false);
107 },
108 );
109
110 it.each(["completed", "interrupted", "cancelled", "rolled_back"] as const)(
111 "retains %s while a runtime request is pending",
112 (status) => {
113 expect(
114 storageCleanupThreadIdle(
115 {
116 ...candidateWithStatus(status),
117 pendingRuntimeRequest: {
118 id: RuntimeRequestId.make("request-1"),
119 kind: "command",
120 createdAt: at(0),
121 },
122 },
123 NOW_MS,
124 ),
125 ).toBe(false);
126 },
127 );
128
87129 it("retains an active run even if the shell status is idle", () => {
88130 expect(
89131 storageCleanupThreadIdle({ ...candidate(), activeRunId: RunId.make("run") }, NOW_MS),
apps/server/src/storageCleanup.ts+6-1
......@@ -86,7 +86,12 @@ export function storageCleanupThreadIdle(thread: OrchestrationV2ThreadShell, now
8686 thread.branch !== null &&
8787 thread.worktreePath !== null &&
8888 thread.activeRunId === null &&
89 (thread.status === "idle" || thread.status === "failed") &&
89 (thread.status === "idle" ||
90 thread.status === "completed" ||
91 thread.status === "interrupted" ||
92 thread.status === "failed" ||
93 thread.status === "cancelled" ||
94 thread.status === "rolled_back") &&
9095 (thread.pendingBackgroundTasks?.length ?? 0) === 0 &&
9196 thread.pendingRuntimeRequest === null &&
9297 !threadHasQueuedTurnStart(thread, now)