From a43e2a10362b95b46a9e49f51a4abea3991d30da Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 9 Apr 2026 16:48:21 +0800 Subject: [PATCH 1/2] fix: ensure HTML report path is flushed to stdout on Windows/Linux (#31) On Windows and Linux, console.log() writes to a non-blocking stdout buffer. The previous setTimeout(() => process.exit(0), 3000) could force-terminate the process before the buffer was flushed, causing Claude to receive an empty path and skip the MEDIA delivery step. Two fixes: 1. Replace console.log + setTimeout/exit with process.stdout.write(path, callback) so the rest of the logic only runs after stdout is confirmed flushed. 2. Use process.exitCode = 0 instead of process.exit() to let Node drain all pending I/O naturally. Also fix Windows browser open: 'start ""' is a cmd.exe built-in and must be called as 'cmd /c start ""' when spawned via child_process.exec. Co-Authored-By: Claude Sonnet 4.6 --- skills/agentguard/scripts/checkup-report.js | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/skills/agentguard/scripts/checkup-report.js b/skills/agentguard/scripts/checkup-report.js index f03bc2c..ca907e3 100644 --- a/skills/agentguard/scripts/checkup-report.js +++ b/skills/agentguard/scripts/checkup-report.js @@ -1364,17 +1364,25 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif} const outPath = join(tmpdir(), `agentguard-checkup-${Date.now()}.html`); writeFileSync(outPath, html, 'utf8'); - console.log(outPath); // Skip browser open for headless/bot environments (Qclaw, OpenClaw, CI) const isHeadless = process.env.OPENCLAW_STATE_DIR || process.env.QCLAW || process.env.CI; - if (!isHeadless) { - const cmd = process.platform === 'darwin' ? 'open' - : process.platform === 'win32' ? 'start ""' - : 'xdg-open'; - exec(`${cmd} "${outPath}"`, (err) => { - if (err) process.stderr.write(`Could not open browser: ${err.message}\n`); - }); - } - setTimeout(() => process.exit(0), 3000); + + // Flush stdout before doing anything else — on Windows/Linux in non-TTY/pipe + // mode, console.log() is non-blocking and process.exit() can terminate before + // the buffer is flushed, causing the caller (Claude) to receive an empty path. + process.stdout.write(outPath + '\n', () => { + if (!isHeadless) { + // 'start ""' is a cmd.exe built-in and must be invoked via cmd /c on Windows + const cmd = process.platform === 'darwin' ? 'open' + : process.platform === 'win32' ? 'cmd /c start ""' + : 'xdg-open'; + exec(`${cmd} "${outPath}"`, (err) => { + if (err) process.stderr.write(`Could not open browser: ${err.message}\n`); + }); + } + // Let Node drain all I/O naturally instead of forcing an early exit. + // process.exitCode avoids the race between setTimeout and stdout flush. + process.exitCode = 0; + }); } From c7737c34d60e031a3613803d140127b1b30aeb9e Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 9 Apr 2026 18:57:37 +0800 Subject: [PATCH 2/2] fix: add .unref() to exit timer to prevent process hang on stuck exec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without .unref(), if exec child process (xdg-open, cmd /c start) hangs, Node would never exit naturally and the 3s setTimeout would be the only exit path — but only if it keeps the event loop alive. With .unref(), the timer does not prevent natural exit but still fires as a hard fallback if exec blocks. Co-Authored-By: Claude Sonnet 4.6 --- skills/agentguard/scripts/checkup-report.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/skills/agentguard/scripts/checkup-report.js b/skills/agentguard/scripts/checkup-report.js index ca907e3..8ebfec0 100644 --- a/skills/agentguard/scripts/checkup-report.js +++ b/skills/agentguard/scripts/checkup-report.js @@ -1368,6 +1368,9 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif} // Skip browser open for headless/bot environments (Qclaw, OpenClaw, CI) const isHeadless = process.env.OPENCLAW_STATE_DIR || process.env.QCLAW || process.env.CI; + // Flush stdout before doing anything else — on Windows/Linux in non-TTY/pipe + // mode, console.log() is non-blocking and process.exit() can terminate before + // the buffer is flushed, causing the caller (Claude) to receive an empty path. // Flush stdout before doing anything else — on Windows/Linux in non-TTY/pipe // mode, console.log() is non-blocking and process.exit() can terminate before // the buffer is flushed, causing the caller (Claude) to receive an empty path. @@ -1381,8 +1384,8 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif} if (err) process.stderr.write(`Could not open browser: ${err.message}\n`); }); } - // Let Node drain all I/O naturally instead of forcing an early exit. - // process.exitCode avoids the race between setTimeout and stdout flush. - process.exitCode = 0; + // Hard exit after 3s — guards against exec child process hanging and + // blocking Node from exiting naturally (e.g. xdg-open on misconfigured Linux). + setTimeout(() => process.exit(0), 3000).unref(); }); }