From ef022aee3d0294726649f52d9a98a86ecd860330 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Thu, 22 Oct 2026 13:21:38 -0500 Subject: [PATCH 1/3] test(server): rotated log cleanup through a symlinked T3 home Co-Authored-By: Claude Opus 5.5 --- apps/server/src/storageCleanup.test.ts | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index 00fabc7004fb8..bf386ba358e3b 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -1627,6 +1627,40 @@ describe("storage cleanup reports and local file policies", () => { }), ), ); + it.live.each([ + { linked: "home", outcome: "removed", reason: "Removed 1 rotated logs" }, + { linked: "logs folder", outcome: "kept", reason: "No expired files" }, + ] as const)("rotated logs behind a symlinked $linked are $outcome", ({ linked, outcome, reason }) => + runCleanupTest( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const config = yield* ServerConfig.ServerConfig; + const disk = yield* fs.realPath(yield* fs.makeTempDirectoryScoped()); + yield* fs.makeDirectory(`${disk}/real/logs`, { recursive: true }); + if (linked === "home") { + yield* fs.symlink(`${disk}/real`, `${disk}/home`); + } else { + yield* fs.makeDirectory(`${disk}/home`); + yield* fs.symlink(`${disk}/real/logs`, `${disk}/home/logs`); + } + yield* fs.writeFileString(`${disk}/real/logs/server.log.1`, "log"); + yield* fs.utimes(`${disk}/real/logs/server.log.1`, 1, 1); + const { service, enableFiles, disable } = yield* cleanupFixture().pipe( + Effect.provideService(ServerConfig.ServerConfig, { + ...config, + logsDir: `${disk}/home/logs`, + }), + ); + disable(); + enableFiles(); + const report = yield* service.runNow; + expect(report.entries.filter((entry) => entry.kind === "logs")).toMatchObject([ + { outcome, reason }, + ]); + expect(yield* fs.exists(`${disk}/real/logs/server.log.1`)).toBe(outcome === "kept"); + }), + ), + ); it.live("reports git failures without aborting the remaining categories", () => runCleanupTest( Effect.gen(function* () { From 53126830071918bccb937311b420483af5291fc0 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Thu, 22 Oct 2026 13:21:39 -0500 Subject: [PATCH 2/3] fix(server): clean rotated logs and browser artifacts when the T3 home is behind a symlink Co-Authored-By: Claude Opus 5.5 --- apps/server/src/storageCleanup.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index a4d1e831d9007..d4d9a4b0e69e7 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -1035,8 +1035,10 @@ const make = Effect.gen(function* () { removed: { files: number; bytes: number }, ) { if (days === null || !(yield* fs.exists(root))) return removed; + // A symlinked parent (a linked T3 home) is fine; a symlinked folder is not. const realRoot = yield* fs.realPath(root); - if (realRoot !== path.resolve(root)) return removed; + const realParent = yield* fs.realPath(path.dirname(root)); + if (realRoot !== path.join(realParent, path.basename(root))) return removed; const visit = Effect.fn("StorageCleanup.visitFiles")(function* ( directory: string, ): Effect.fn.Return { From 2d27f1164ababae117498c9cfd0bef8e43992bc6 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Thu, 22 Oct 2026 13:27:06 -0500 Subject: [PATCH 3/3] test(server): format the symlinked log cleanup test Co-Authored-By: Claude Opus 5.5 --- apps/server/src/storageCleanup.test.ts | 62 +++++++++++++------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index bf386ba358e3b..d9f126f055dda 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -1630,36 +1630,38 @@ describe("storage cleanup reports and local file policies", () => { it.live.each([ { linked: "home", outcome: "removed", reason: "Removed 1 rotated logs" }, { linked: "logs folder", outcome: "kept", reason: "No expired files" }, - ] as const)("rotated logs behind a symlinked $linked are $outcome", ({ linked, outcome, reason }) => - runCleanupTest( - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const config = yield* ServerConfig.ServerConfig; - const disk = yield* fs.realPath(yield* fs.makeTempDirectoryScoped()); - yield* fs.makeDirectory(`${disk}/real/logs`, { recursive: true }); - if (linked === "home") { - yield* fs.symlink(`${disk}/real`, `${disk}/home`); - } else { - yield* fs.makeDirectory(`${disk}/home`); - yield* fs.symlink(`${disk}/real/logs`, `${disk}/home/logs`); - } - yield* fs.writeFileString(`${disk}/real/logs/server.log.1`, "log"); - yield* fs.utimes(`${disk}/real/logs/server.log.1`, 1, 1); - const { service, enableFiles, disable } = yield* cleanupFixture().pipe( - Effect.provideService(ServerConfig.ServerConfig, { - ...config, - logsDir: `${disk}/home/logs`, - }), - ); - disable(); - enableFiles(); - const report = yield* service.runNow; - expect(report.entries.filter((entry) => entry.kind === "logs")).toMatchObject([ - { outcome, reason }, - ]); - expect(yield* fs.exists(`${disk}/real/logs/server.log.1`)).toBe(outcome === "kept"); - }), - ), + ] as const)( + "rotated logs behind a symlinked $linked are $outcome", + ({ linked, outcome, reason }) => + runCleanupTest( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const config = yield* ServerConfig.ServerConfig; + const disk = yield* fs.realPath(yield* fs.makeTempDirectoryScoped()); + yield* fs.makeDirectory(`${disk}/real/logs`, { recursive: true }); + if (linked === "home") { + yield* fs.symlink(`${disk}/real`, `${disk}/home`); + } else { + yield* fs.makeDirectory(`${disk}/home`); + yield* fs.symlink(`${disk}/real/logs`, `${disk}/home/logs`); + } + yield* fs.writeFileString(`${disk}/real/logs/server.log.1`, "log"); + yield* fs.utimes(`${disk}/real/logs/server.log.1`, 1, 1); + const { service, enableFiles, disable } = yield* cleanupFixture().pipe( + Effect.provideService(ServerConfig.ServerConfig, { + ...config, + logsDir: `${disk}/home/logs`, + }), + ); + disable(); + enableFiles(); + const report = yield* service.runNow; + expect(report.entries.filter((entry) => entry.kind === "logs")).toMatchObject([ + { outcome, reason }, + ]); + expect(yield* fs.exists(`${disk}/real/logs/server.log.1`)).toBe(outcome === "kept"); + }), + ), ); it.live("reports git failures without aborting the remaining categories", () => runCleanupTest(