From 2e1d3e735d624bf284bad964508905f5e42970fa Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 6 Oct 2026 18:14:41 +0000 Subject: [PATCH] fix: Codex watchdog freezes the command before reaping its children The bash-native watchdog (stock macOS, no timeout binary) ran pkill -P on the command's children and then killed the command. Between the two, the command continued past its killed child and printed output after its deadline. Stop it first. The test's minimal PATH also lacked pkill, which the watchdog uses to reap children (stock macOS ships it), so the orphaned child held stdout open and the capture waited out its sleep. --- bin/gstack-codex-probe | 3 +++ test/codex-hardening.test.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/gstack-codex-probe b/bin/gstack-codex-probe index 772d2487e..dcc9e3bc2 100755 --- a/bin/gstack-codex-probe +++ b/bin/gstack-codex-probe @@ -418,6 +418,9 @@ _gstack_codex_timeout_wrapper() { sleep 1 _GSTACK_CODEX_WAITED=$((_GSTACK_CODEX_WAITED + 1)) done + # Freeze the command first: killing its children while it runs lets it + # continue past the killed child and print output after its deadline. + kill -STOP "$_cmd_pid" 2>/dev/null pkill -KILL -P "$_cmd_pid" 2>/dev/null kill -KILL "$_cmd_pid" 2>/dev/null exit 124 diff --git a/test/codex-hardening.test.ts b/test/codex-hardening.test.ts index bb345a4a5..c0e8efdc4 100644 --- a/test/codex-hardening.test.ts +++ b/test/codex-hardening.test.ts @@ -357,7 +357,10 @@ describe('gstack-codex-probe: timeout wrapper + namespace hygiene', () => { // tool gate killed the whole call and the partial output was lost. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-stubborn-')); try { - for (const tool of native ? ['bash', 'sleep', 'cat'] : []) { + // pkill is part of the watchdog's contract (it reaps the provider's + // children; stock macOS ships /usr/bin/pkill). Without it the orphaned + // `sleep 30` keeps stdout open and the capture waits out the sleep. + for (const tool of native ? ['bash', 'sleep', 'cat', 'pkill'] : []) { const resolved = spawnSync('bash', ['-c', `command -v ${tool}`], { timeout: 5000 }); fs.symlinkSync(resolved.stdout.toString().trim(), path.join(dir, tool)); }