fix: filter codex ReasoningSummary stderr noise on exit code 0
Codex writes informational ERROR messages about ReasoningSummaryDelta to stderr even on successful execution (exit code 0). The OpenClaw agent misinterprets this non-empty stderr as a failure. - Add filterStderrNoise() to strip known codex noise patterns from stderr when exit code is 0 - Preserve raw stderr in DebugInfo.rawStderr when --debug is active - Add 5 new tests covering noise filtering, preservation on failure, debug raw output, and non-codex client passthrough
This commit is contained in:
@@ -9,6 +9,28 @@ export const CLIENT_ARGS: Record<ClientName, (prompt: string) => string[]> = {
|
||||
opencode: (p) => ["run", "--dangerously-skip-permissions", p],
|
||||
};
|
||||
|
||||
/**
|
||||
* Known stderr noise patterns per client.
|
||||
* When exit code is 0, lines matching these patterns are stripped from the
|
||||
* returned stderr to prevent agents from misinterpreting informational
|
||||
* diagnostics as errors. The raw (unfiltered) stderr is preserved in
|
||||
* DebugInfo.rawStderr when --debug is active.
|
||||
*/
|
||||
const STDERR_NOISE_PATTERNS: Partial<Record<ClientName, RegExp[]>> = {
|
||||
codex: [
|
||||
/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+ERROR\s+codex_core::util:\s+ReasoningSummary\w*\s+/,
|
||||
],
|
||||
};
|
||||
|
||||
function filterStderrNoise(client: ClientName, stderr: string, exitCode: number): string {
|
||||
if (exitCode !== 0) return stderr;
|
||||
const patterns = STDERR_NOISE_PATTERNS[client];
|
||||
if (!patterns) return stderr;
|
||||
const lines = stderr.split("\n");
|
||||
const filtered = lines.filter((line) => !patterns.some((p) => p.test(line)));
|
||||
return filtered.join("\n").replace(/\n+$/, "");
|
||||
}
|
||||
|
||||
export async function executePrompt(
|
||||
client: ClientName,
|
||||
prompt: string,
|
||||
@@ -78,6 +100,8 @@ export async function executePrompt(
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
const durationMs = Date.now() - startMs;
|
||||
const rawStderr = stderr;
|
||||
const cleanedStderr = filterStderrNoise(client, rawStderr, result?.exitCode ?? -1);
|
||||
if (options.debug || options.onDebug) {
|
||||
const effectiveExitCode = result?.exitCode ?? (err instanceof ExecError ? err.result.exitCode : null);
|
||||
const debugInfo: DebugInfo = {
|
||||
@@ -87,20 +111,22 @@ export async function executePrompt(
|
||||
exitCode: effectiveExitCode,
|
||||
exitSignal,
|
||||
durationMs,
|
||||
stderrLength: stderr.length,
|
||||
stderrLength: rawStderr.length,
|
||||
stdoutLength: stdout.length,
|
||||
noisySuccess: effectiveExitCode === 0 && stderr.length > 0,
|
||||
noisySuccess: effectiveExitCode === 0 && rawStderr.length > 0,
|
||||
rawStderr: rawStderr !== cleanedStderr ? rawStderr : undefined,
|
||||
};
|
||||
options.onDebug?.(debugInfo);
|
||||
}
|
||||
if (err) {
|
||||
if (err instanceof ExecError) {
|
||||
err.result.stderr = cleanedStderr;
|
||||
err.result.client = client;
|
||||
err.result.durationMs = durationMs;
|
||||
}
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ ...result!, client, durationMs });
|
||||
resolve({ ...result!, client, durationMs, stderr: cleanedStderr });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user