From fa10f11e09cf5f2b5ab779d082a557c203279608 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:35:01 -0700 Subject: [PATCH] fix(codex): keep standalone home tripwire active --- .../scripts/codex-primary-home-tripwire.mjs | 3 +- .../codex-primary-home-tripwire.test.mjs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/config/scripts/codex-primary-home-tripwire.mjs b/config/scripts/codex-primary-home-tripwire.mjs index e5e8bd19e52..e03596d3cc3 100644 --- a/config/scripts/codex-primary-home-tripwire.mjs +++ b/config/scripts/codex-primary-home-tripwire.mjs @@ -125,7 +125,8 @@ export async function startCodexPrimaryHomeTripwire(options = {}) { } timer = setInterval(() => void scan(), intervalMs) - timer.unref?.() + // Why: this safety monitor must remain active even when it is the CLI's only open handle. + // Callers explicitly stop it after their validation window closes. return { codexHome, initialSnapshot, diff --git a/config/scripts/codex-primary-home-tripwire.test.mjs b/config/scripts/codex-primary-home-tripwire.test.mjs index 032c59862aa..6e9e81fc09c 100644 --- a/config/scripts/codex-primary-home-tripwire.test.mjs +++ b/config/scripts/codex-primary-home-tripwire.test.mjs @@ -1,6 +1,8 @@ +import { spawn } from 'node:child_process' import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' +import { fileURLToPath } from 'node:url' import { afterEach, describe, expect, it } from 'vitest' import { describeCodexHomeChange, @@ -24,6 +26,22 @@ async function createPrimaryHome() { } describe('Codex primary-home tripwire', () => { + it('keeps the standalone monitor alive until it is stopped', async () => { + const primaryHome = await createPrimaryHome() + const scriptPath = fileURLToPath(new URL('./codex-primary-home-tripwire.mjs', import.meta.url)) + const child = spawn(process.execPath, [scriptPath, '--primary-home', primaryHome], { + stdio: ['ignore', 'pipe', 'pipe'] + }) + + try { + await waitForStdout(child, '[CODEX HOME TRIPWIRE] Watching') + await new Promise((resolve) => setTimeout(resolve, 100)) + expect(child.exitCode).toBeNull() + } finally { + child.kill() + } + }) + it('detects a write without reading file contents', async () => { const primaryHome = await createPrimaryHome() const tripwire = await startCodexPrimaryHomeTripwire({ primaryHome, intervalMs: 25 }) @@ -52,3 +70,25 @@ describe('Codex primary-home tripwire', () => { expect(describeCodexHomeChange(before, after)).toBeNull() }) }) + +function waitForStdout(child, expectedText) { + return new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error(`Timed out waiting for ${expectedText}`)), + 2_000 + ) + let output = '' + child.stdout.setEncoding('utf8') + child.stdout.on('data', (chunk) => { + output += chunk + if (output.includes(expectedText)) { + clearTimeout(timeout) + resolve() + } + }) + child.once('exit', (code) => { + clearTimeout(timeout) + reject(new Error(`Tripwire exited early with code ${code}`)) + }) + }) +}