From 1983dd82e7614198ff90b537583bb51a837bf0f7 Mon Sep 17 00:00:00 2001 From: Stefano Fiorini Date: Tue, 19 May 2026 19:51:10 -0500 Subject: [PATCH] feat(S-104): Add stderr-length and exit-code correlation diagnostics --- tools/ai-cli-dispatch/src/execute.ts | 4 +- tools/ai-cli-dispatch/src/types.ts | 1 + tools/ai-cli-dispatch/tests/execute.test.ts | 42 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/tools/ai-cli-dispatch/src/execute.ts b/tools/ai-cli-dispatch/src/execute.ts index cf39ba3..b443e6a 100644 --- a/tools/ai-cli-dispatch/src/execute.ts +++ b/tools/ai-cli-dispatch/src/execute.ts @@ -90,15 +90,17 @@ export async function executePrompt( clearTimeout(timeout); const durationMs = Date.now() - startMs; if (options.debug || options.onDebug) { + const effectiveExitCode = result?.exitCode ?? (err instanceof ExecError ? err.result.exitCode : null); const debugInfo: DebugInfo = { command, args, pid: child.pid ?? undefined, - exitCode: result?.exitCode ?? (err instanceof ExecError ? err.result.exitCode : null), + exitCode: effectiveExitCode, exitSignal, durationMs, stderrLength: stderr.length, stdoutLength: stdout.length, + noisySuccess: effectiveExitCode === 0 && stderr.length > 0, }; options.onDebug?.(debugInfo); } diff --git a/tools/ai-cli-dispatch/src/types.ts b/tools/ai-cli-dispatch/src/types.ts index 166e651..82008ce 100644 --- a/tools/ai-cli-dispatch/src/types.ts +++ b/tools/ai-cli-dispatch/src/types.ts @@ -24,6 +24,7 @@ export interface DebugInfo { durationMs: number; stderrLength: number; stdoutLength: number; + noisySuccess: boolean; } export interface DispatchConfigFile { diff --git a/tools/ai-cli-dispatch/tests/execute.test.ts b/tools/ai-cli-dispatch/tests/execute.test.ts index e8410e8..c2ea7df 100644 --- a/tools/ai-cli-dispatch/tests/execute.test.ts +++ b/tools/ai-cli-dispatch/tests/execute.test.ts @@ -320,4 +320,46 @@ describe("executePrompt", () => { assert.strictEqual(debugInfos[0].exitCode, null); assert.strictEqual(debugInfos[0].exitSignal, null); }); + + it("reports noisySuccess=true when stderr is non-empty and exitCode is 0", async () => { + const scenarios = new Map([ + ["codex exec --yolo hello", { stdout: "ok", stderr: "warn", exitCode: 0 }], + ]); + const debugInfos: any[] = []; + await executePrompt("codex", "hello", { + spawn: mockSpawn(scenarios), + existsSync: () => true, + debug: true, + onDebug: (info) => debugInfos.push(info), + }); + assert.strictEqual(debugInfos[0].noisySuccess, true); + }); + + it("reports noisySuccess=false when stderr is empty and exitCode is 0", async () => { + const scenarios = new Map([ + ["codex exec --yolo hello", { stdout: "ok", stderr: "", exitCode: 0 }], + ]); + const debugInfos: any[] = []; + await executePrompt("codex", "hello", { + spawn: mockSpawn(scenarios), + existsSync: () => true, + debug: true, + onDebug: (info) => debugInfos.push(info), + }); + assert.strictEqual(debugInfos[0].noisySuccess, false); + }); + + it("reports noisySuccess=false when exitCode is non-zero even if stderr is non-empty", async () => { + const scenarios = new Map([ + ["codex exec --yolo fail", { stdout: "", stderr: "error", exitCode: 1 }], + ]); + const debugInfos: any[] = []; + await executePrompt("codex", "fail", { + spawn: mockSpawn(scenarios), + existsSync: () => true, + debug: true, + onDebug: (info) => debugInfos.push(info), + }); + assert.strictEqual(debugInfos[0].noisySuccess, false); + }); });