From e20ca33b80f231f3f8c16ab57ae7864d666bb336 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 21:22:50 +0000 Subject: [PATCH 1/2] test(server): reproduce storage cleanup failing on a retained worktree without .git (#31136) A thread's retained worktree path that still exists but has no .git (a git-removed worktree recreated with only yarn.lock and node_modules) should be kept as "Not a linked worktree". On main, cleanup stats the missing .git and reports a failed NotFound entry instead. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/storageCleanup.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index b4c9ac3e68945..eb45c15f0edb4 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -2214,6 +2214,26 @@ describe("storage cleanup reports and local file policies", () => { }), ), ); + it.live("keeps a thread's recreated worktree folder that has no .git", () => + runCleanupTest( + Effect.gen(function* () { + const fixture = yield* cleanupFixture(); + yield* fixture.command(fixture.repo, ["worktree", "remove", fixture.worktree]); + yield* fixture.fs.makeDirectory(`${fixture.worktree}/node_modules`, { recursive: true }); + yield* fixture.fs.writeFileString(`${fixture.worktree}/yarn.lock`, "# lockfile\n"); + expect((yield* fixture.service.runNow).entries).toEqual([ + expect.objectContaining({ + path: fixture.worktree, + outcome: "kept", + reason: "Not a linked worktree", + }), + ]); + expect(yield* fixture.fs.readFileString(`${fixture.worktree}/yarn.lock`)).toBe( + "# lockfile\n", + ); + }), + ), + ); it.live.each([ ["any-local-files", ".env", "kept"], ["uncommitted-changes", ".env", "removed"], From d074122895585fde84db4b016fcc99e9d78cee36 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 21:34:19 +0000 Subject: [PATCH 2/2] fix(server): storage cleanup keeps a retained worktree folder without .git A thread's worktree folder that exists but has no .git entry (for example one removed with `git worktree remove` and recreated by a package manager) made the unguarded stat fail with NotFound, so every cleanup run recorded a failure for it. Check for .git with fs.exists first, matching the orphan scan, so the folder is kept as "Not a linked worktree". fs.exists only maps NotFound to false, so other filesystem errors still fail the check. Fixes pingdotgg/t3code#31136 AI-assisted; verified with the tests and tools listed above. --- apps/server/src/storageCleanup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index 9714d9175afdf..b2de1e1a01e53 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -898,7 +898,8 @@ const make = Effect.gen(function* () { return keep("outside the managed worktree folder"); if (yield* containsProjectRoot(worktreePath, [project, ...snapshot.projects])) return keep("contains a project checkout"); - if ((yield* fs.stat(path.join(worktreePath, ".git"))).type !== "File") + const gitPath = path.join(worktreePath, ".git"); + if (!(yield* fs.exists(gitPath)) || (yield* fs.stat(gitPath)).type !== "File") return keep("not a linked worktree"); if (yield* branchChanged(worktreePath, thread.branch)) return keep("repository or branch changed");