From e75dceb260da0b0aadf4c959619e64928d6c7400 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sat, 7 Mar 2026 19:32:04 -0500 Subject: [PATCH] fix(opencode): sort log files before cleanup and fix guard threshold --- packages/opencode/src/util/log.ts | 8 ++-- packages/opencode/test/util/log.test.ts | 52 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 packages/opencode/test/util/log.test.ts diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index 2ca4c0a3de30..9f5148176145 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -59,7 +59,7 @@ export namespace Log { export async function init(options: Options) { if (options.level) level = options.level - cleanup(Global.Path.log) + await cleanup(Global.Path.log) if (options.print) return logpath = path.join( Global.Path.log, @@ -77,15 +77,15 @@ export namespace Log { } } - async function cleanup(dir: string) { + export async function cleanup(dir: string) { const files = await Glob.scan("????-??-??T??????.log", { cwd: dir, absolute: true, include: "file", }) - if (files.length <= 5) return + if (files.length <= 10) return - const filesToDelete = files.slice(0, -10) + const filesToDelete = files.sort().slice(0, -10) await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {}))) } diff --git a/packages/opencode/test/util/log.test.ts b/packages/opencode/test/util/log.test.ts new file mode 100644 index 000000000000..68f2035ecc76 --- /dev/null +++ b/packages/opencode/test/util/log.test.ts @@ -0,0 +1,52 @@ +import { describe, test, expect } from "bun:test" +import path from "path" +import fs from "fs/promises" +import { tmpdir } from "../fixture/fixture" +import { Log } from "../../src/util/log" +import { Glob } from "../../src/util/glob" + +function logname(ts: string) { + return `${ts}.log` +} + +async function touch(dir: string, name: string) { + await fs.writeFile(path.join(dir, name), "") +} + +describe("log cleanup", () => { + test("does nothing when at or below threshold", async () => { + await using tmp = await tmpdir() + const names = Array.from({ length: 10 }, (_, i) => logname(`2026-01-${String(i + 1).padStart(2, "0")}T000000`)) + for (const n of names) await touch(tmp.path, n) + + await Log.cleanup(tmp.path) + + expect((await Glob.scan("*.log", { cwd: tmp.path })).sort()).toEqual(names.sort()) + }) + + test("removes oldest files keeping newest 10 (regression: unsorted glob)", async () => { + await using tmp = await tmpdir() + const names = Array.from({ length: 15 }, (_, i) => logname(`2026-01-${String(i + 1).padStart(2, "0")}T000000`)) + for (const n of names) await touch(tmp.path, n) + + await Log.cleanup(tmp.path) + + const remaining = (await Glob.scan("*.log", { cwd: tmp.path })).sort() + expect(remaining).toEqual(names.sort().slice(5)) + }) + + test("does not delete dev.log", async () => { + await using tmp = await tmpdir() + for (let i = 1; i <= 12; i++) await touch(tmp.path, logname(`2026-01-${String(i).padStart(2, "0")}T000000`)) + await touch(tmp.path, "dev.log") + + await Log.cleanup(tmp.path) + + expect(await fs.stat(path.join(tmp.path, "dev.log"))).toBeDefined() + }) + + test("handles missing log directory gracefully", async () => { + await using tmp = await tmpdir() + await expect(Log.cleanup(path.join(tmp.path, "does-not-exist"))).resolves.toBeUndefined() + }) +})