diff --git a/plugins/codex/scripts/lib/broker-lifecycle.mjs b/plugins/codex/scripts/lib/broker-lifecycle.mjs index ef763819c..09a5d84cb 100644 --- a/plugins/codex/scripts/lib/broker-lifecycle.mjs +++ b/plugins/codex/scripts/lib/broker-lifecycle.mjs @@ -180,11 +180,19 @@ export function teardownBrokerSession({ endpoint = null, pidFile, logFile, sessi } if (pidFile && fs.existsSync(pidFile)) { - fs.unlinkSync(pidFile); + try { + fs.unlinkSync(pidFile); + } catch { + // Ignore locked or already-removed pid files during teardown. + } } if (logFile && fs.existsSync(logFile)) { - fs.unlinkSync(logFile); + try { + fs.unlinkSync(logFile); + } catch { + // Ignore locked or already-removed log files during teardown. + } } if (endpoint) { diff --git a/tests/broker-lifecycle.test.mjs b/tests/broker-lifecycle.test.mjs new file mode 100644 index 000000000..82c15c29e --- /dev/null +++ b/tests/broker-lifecycle.test.mjs @@ -0,0 +1,42 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { teardownBrokerSession } from "../plugins/codex/scripts/lib/broker-lifecycle.mjs"; + +test("teardownBrokerSession ignores EPERM unlink failures for pid/log files", () => { + const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-broker-teardown-")); + const pidFile = path.join(sessionDir, "broker.pid"); + const logFile = path.join(sessionDir, "broker.log"); + const socketPath = path.join(sessionDir, "broker.sock"); + fs.writeFileSync(pidFile, "123\n", "utf8"); + fs.writeFileSync(logFile, "log\n", "utf8"); + fs.writeFileSync(socketPath, "", "utf8"); + + const originalUnlinkSync = fs.unlinkSync; + fs.unlinkSync = (target) => { + if (target === pidFile || target === logFile) { + const error = new Error(`EPERM: operation not permitted, unlink '${target}'`); + error.code = "EPERM"; + throw error; + } + return originalUnlinkSync(target); + }; + + try { + assert.doesNotThrow(() => + teardownBrokerSession({ + endpoint: `unix:${socketPath}`, + pidFile, + logFile, + sessionDir, + }), + ); + assert.equal(fs.existsSync(socketPath), false); + } finally { + fs.unlinkSync = originalUnlinkSync; + fs.rmSync(sessionDir, { recursive: true, force: true }); + } +});