From f6d2f3376d3429826aa9a76ec6e9c3f9dfdc8346 Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 6 Oct 2026 20:43:07 +0000 Subject: [PATCH] test: deterministic regression for the Codex watchdog freezing the command before reaping its children The existing stubborn-child test caught the reap-then-kill race only some of the time (2 of 3 runs on the pre-fix watchdog). A pkill that sleeps after reaping widens the window between reaping the children and killing the command, so a watchdog that does not SIGSTOP the command first always lets it print "late" after the deadline. Fails 3 of 3 on the pre-fix watchdog, passes 3 of 3 on the current one. --- test/codex-hardening.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/codex-hardening.test.ts b/test/codex-hardening.test.ts index c0e8efdc4..120a217ee 100644 --- a/test/codex-hardening.test.ts +++ b/test/codex-hardening.test.ts @@ -378,6 +378,27 @@ describe('gstack-codex-probe: timeout wrapper + namespace hygiene', () => { } }); + test('bash-native watchdog freezes the command before reaping its children, so nothing prints after the deadline', () => { + // A slow pkill widens the window between reaping the children and killing + // the command: a watchdog that does not stop the command first lets it run + // on past its killed child and print after the deadline. + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-freeze-')); + try { + const which = (tool: string) => spawnSync('bash', ['-c', `command -v ${tool}`], { timeout: 5000 }).stdout.toString().trim(); + for (const tool of ['bash', 'sleep', 'cat']) fs.symlinkSync(which(tool), path.join(dir, tool)); + fs.writeFileSync(path.join(dir, 'pkill'), `#!${which('bash')}\n"${which('pkill')}" "$@"\nrc=$?\n"${which('sleep')}" 0.5\nexit "$rc"\n`, { mode: 0o755 }); + const stubborn = path.join(dir, 'stubborn'); + fs.writeFileSync(stubborn, `#!${which('bash')}\ntrap '' TERM\necho partial\nsleep 30\necho late\n`, { mode: 0o755 }); + const r = runProbe({ + snippet: `_GSTACK_CODEX_KILL_AFTER=1 _gstack_codex_timeout_wrapper 1 "${stubborn}"; echo "rc=$?"`, + env: { PATH: dir }, + }); + expect(r.stdout).toBe('partial\nrc=124\n'); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + for (const native of [false, true]) test(`E5: ${native ? 'bash-native watchdog' : 'timeout(1)'} passes the caller's stdin to the command (#1674)`, () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-stdin-')); try {