From f5c6c1d94ccca731ee44a50c320da8b8b6d35863 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 9 Apr 2026 15:49:27 -0500 Subject: [PATCH] fix(gsd): suppress workflow stderr during /gsd --- .../extensions/gsd/commands/index.ts | 8 ++++++- .../gsd/tests/workflow-logger.test.ts | 16 ++++++++++++++ .../extensions/gsd/workflow-logger.ts | 22 ++++++++++++++++--- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/resources/extensions/gsd/commands/index.ts b/src/resources/extensions/gsd/commands/index.ts index 38f55e0bb..c07476532 100644 --- a/src/resources/extensions/gsd/commands/index.ts +++ b/src/resources/extensions/gsd/commands/index.ts @@ -8,7 +8,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void { getArgumentCompletions: getGsdArgumentCompletions, handler: async (args: string, ctx: ExtensionCommandContext) => { const { handleGSDCommand } = await import("./dispatcher.js"); - await handleGSDCommand(args, ctx, pi); + const { setStderrLoggingEnabled } = await import("../workflow-logger.js"); + const previousStderrSetting = setStderrLoggingEnabled(false); + try { + await handleGSDCommand(args, ctx, pi); + } finally { + setStderrLoggingEnabled(previousStderrSetting); + } }, }); } diff --git a/src/resources/extensions/gsd/tests/workflow-logger.test.ts b/src/resources/extensions/gsd/tests/workflow-logger.test.ts index 68ef6710f..9af623bd5 100644 --- a/src/resources/extensions/gsd/tests/workflow-logger.test.ts +++ b/src/resources/extensions/gsd/tests/workflow-logger.test.ts @@ -18,6 +18,7 @@ import { summarizeLogs, formatForNotification, setLogBasePath, + setStderrLoggingEnabled, _resetLogs, } from "../workflow-logger.ts"; @@ -375,5 +376,20 @@ describe("workflow-logger", () => { logError("tool", "failed", { cmd: "complete_task" }); assert.ok(written[0].includes('"cmd":"complete_task"')); }); + + test("suppresses stderr when disabled", (t) => { + const written: string[] = []; + const orig = process.stderr.write.bind(process.stderr); + const previous = setStderrLoggingEnabled(false); + // @ts-ignore — patching for test + process.stderr.write = (chunk: string) => { written.push(chunk); return true; }; + t.after(() => { + process.stderr.write = orig; + setStderrLoggingEnabled(previous); + }); + + logWarning("engine", "hidden warning"); + assert.deepEqual(written, []); + }); }); }); diff --git a/src/resources/extensions/gsd/workflow-logger.ts b/src/resources/extensions/gsd/workflow-logger.ts index e4d62b39b..cdff396a3 100644 --- a/src/resources/extensions/gsd/workflow-logger.ts +++ b/src/resources/extensions/gsd/workflow-logger.ts @@ -67,6 +67,7 @@ export interface LogEntry { const MAX_BUFFER = 100; let _buffer: LogEntry[] = []; let _auditBasePath: string | null = null; +let _stderrEnabled = true; /** * Set the base path for persistent audit log writes. @@ -77,6 +78,16 @@ export function setLogBasePath(basePath: string): void { _auditBasePath = basePath; } +/** + * Enable or disable immediate stderr writes for workflow logs. + * Returns the previous setting so callers can restore it. + */ +export function setStderrLoggingEnabled(enabled: boolean): boolean { + const previous = _stderrEnabled; + _stderrEnabled = enabled; + return previous; +} + // ─── Public API ───────────────────────────────────────────────────────── /** @@ -245,7 +256,7 @@ function _push( // Always forward to stderr so terminal watchers see it (see module header for policy) const prefix = severity === "error" ? "ERROR" : "WARN"; const ctxStr = context ? ` ${JSON.stringify(context)}` : ""; - process.stderr.write(`[gsd:${component}] ${prefix}: ${message}${ctxStr}\n`); + _writeStderr(`[gsd:${component}] ${prefix}: ${message}${ctxStr}\n`); // Persist to notification store (both warnings and errors) try { @@ -255,7 +266,7 @@ function _push( "workflow-logger", ); } catch (notifErr) { - process.stderr.write(`[gsd:workflow-logger] notification-store append failed: ${(notifErr as Error).message}\n`); + _writeStderr(`[gsd:workflow-logger] notification-store append failed: ${(notifErr as Error).message}\n`); } // Buffer for auto-loop to drain @@ -275,11 +286,16 @@ function _push( appendFileSync(join(auditDir, "audit-log.jsonl"), JSON.stringify(sanitized) + "\n", "utf-8"); } catch (auditErr) { // Best-effort — never let audit write failures bubble up - process.stderr.write(`[gsd:audit] failed to persist log entry: ${(auditErr as Error).message}\n`); + _writeStderr(`[gsd:audit] failed to persist log entry: ${(auditErr as Error).message}\n`); } } } +function _writeStderr(message: string): void { + if (!_stderrEnabled) return; + process.stderr.write(message); +} + /** * Sanitize a log entry before persisting to the audit JSONL file. * Strips potentially sensitive context (raw paths, cwd, full error text)