From 17486d2009d9cf09e87edcd26e8b5bd6f6729554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korm=C3=A1kur?= Date: Sat, 11 Jul 2026 00:38:14 +0000 Subject: [PATCH] fix(utils): create log dir before redirecting stderr guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a fresh profile ~/.omp/logs may not exist yet (the logger creates it lazily), so opening getLogPath() with "a" threw and the guard fell back to /dev/null — discarding macOS diagnostics and native crash reports instead of preserving them in the omp log. mkdir the redirect target's parent (recursive) before opening; the /dev/null fallback stays as the safety net. --- packages/utils/src/stderr-guard.ts | 8 ++++- packages/utils/test/stderr-guard.test.ts | 44 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/stderr-guard.ts b/packages/utils/src/stderr-guard.ts index 693552c27..fc1643b9c 100644 --- a/packages/utils/src/stderr-guard.ts +++ b/packages/utils/src/stderr-guard.ts @@ -23,6 +23,7 @@ */ import { dlopen, FFIType } from "bun:ffi"; import * as fs from "node:fs"; +import * as path from "node:path"; import { getLogPath } from "./dirs"; const STDOUT_FILENO = 1; @@ -101,7 +102,12 @@ export function suppressTerminalStderr(options?: SuppressTerminalStderrOptions): let redirectFd: number; try { - redirectFd = fs.openSync(options?.redirectPath ?? getLogPath(), "a"); + const redirectPath = options?.redirectPath ?? getLogPath(); + // getLogsDir() only computes the path; the logger creates it lazily, so + // on a fresh profile ~/.omp/logs may not exist yet. Create it here so + // diagnostics land in the log instead of falling through to /dev/null. + fs.mkdirSync(path.dirname(redirectPath), { recursive: true }); + redirectFd = fs.openSync(redirectPath, "a"); } catch { try { redirectFd = fs.openSync("/dev/null", "w"); diff --git a/packages/utils/test/stderr-guard.test.ts b/packages/utils/test/stderr-guard.test.ts index fa663d73f..de13e706e 100644 --- a/packages/utils/test/stderr-guard.test.ts +++ b/packages/utils/test/stderr-guard.test.ts @@ -94,4 +94,48 @@ describe("stderr guard", () => { expect(fs.existsSync(redirectPath)).toBe(false); } }); + + it("creates the redirect target's parent directory when it does not exist", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "stderr-guard-")); + tempDirs.push(dir); + // Nested path whose parent dirs do not exist yet — mirrors a fresh + // profile where ~/.omp/logs has not been created by the logger. + const missingParent = path.join(dir, "nested", "deep"); + const redirectPath = path.join(missingParent, "redirect.log"); + const probePath = path.join(dir, "probe.ts"); + fs.writeFileSync( + probePath, + [ + `import { restoreTerminalStderr, suppressTerminalStderr } from ${JSON.stringify(GUARD_MODULE)};`, + `import * as fs from "node:fs";`, + `const redirectPath = process.argv[2];`, + `const forced = suppressTerminalStderr({ force: true, redirectPath });`, + `if (forced) fs.writeSync(2, "hidden\\n");`, + `restoreTerminalStderr();`, + `process.stdout.write(JSON.stringify({ forced }));`, + ].join("\n"), + ); + + const proc = Bun.spawn([process.execPath, probePath, redirectPath], { + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, exitCode] = await Promise.all([ + new Response(proc.stdout as ReadableStream).text(), + proc.exited, + ]); + + expect(exitCode).toBe(0); + const { forced } = JSON.parse(stdout) as { forced: boolean }; + if (forced) { + // The guard must have created the missing parent and written there, + // not fallen back to /dev/null. + expect(fs.existsSync(missingParent)).toBe(true); + expect(fs.readFileSync(redirectPath, "utf8")).toBe("hidden\n"); + } else { + // Inert (no libc fd ops): returns before opening the redirect, so the + // parent is never created. + expect(fs.existsSync(missingParent)).toBe(false); + } + }); });