diff --git a/lib/providers/anthropic.mjs b/lib/providers/anthropic.mjs index 45a6d8d..a249990 100644 --- a/lib/providers/anthropic.mjs +++ b/lib/providers/anthropic.mjs @@ -967,6 +967,19 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl, isolationCtx) stdio: ['pipe', 'pipe', 'pipe'], }); + // Guard stdin writes against EPIPE (child may close stdin before we finish + // writing — e.g. on auth error, bad model, or oversized prompt). + // Port of OCP server.mjs:780. + // Authority: claude CLI v2.1.104 observed behaviour — fast-failing spawn closes + // stdin before OLP finishes writing; Node stdin Writable surfaces EPIPE. + // ALIGNMENT.md Rule 2: no CLI operation changed; guard is a Node stream safety net. + // The typeof guard protects test-injected mock stdin objects that are plain + // objects (not EventEmitters) and do not implement .on(). + if (typeof proc.stdin.on === 'function') { + proc.stdin.on('error', (e) => { + console.error(JSON.stringify({ ts: new Date().toISOString(), level: 'warn', event: 'stdin_write_error', error: e.message })); + }); + } // OCP server.mjs:586-587: proc.stdin.write(prompt); proc.stdin.end(); proc.stdin.write(prompt); proc.stdin.end(); diff --git a/lib/providers/codex.mjs b/lib/providers/codex.mjs index d005387..c3aad10 100644 --- a/lib/providers/codex.mjs +++ b/lib/providers/codex.mjs @@ -505,6 +505,19 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl, isolationCtx) const proc = spawnImpl(finalBin, finalArgs, { env: finalEnv, stdio: ['pipe', 'pipe', 'pipe'] }); + // Guard stdin writes against EPIPE (child may close stdin before we finish + // writing — e.g. on auth error, bad model, or oversized prompt). + // Port of OCP server.mjs:780. + // Authority: codex CLI Codex CLI reference (https://developers.openai.com/codex/cli/reference) + // observed behaviour — fast-failing spawn closes stdin before OLP finishes writing. + // ALIGNMENT.md Rule 2: no CLI operation changed; guard is a Node stream safety net. + // The typeof guard protects test-injected mock stdin objects that are plain + // objects (not EventEmitters) and do not implement .on(). + if (typeof proc.stdin.on === 'function') { + proc.stdin.on('error', (e) => { + console.error(JSON.stringify({ ts: new Date().toISOString(), level: 'warn', event: 'stdin_write_error', error: e.message })); + }); + } // Write prompt via stdin for multi-line prompts (D6 assumption A1) if (useStdin) { proc.stdin.write(prompt); diff --git a/lib/providers/mistral.mjs b/lib/providers/mistral.mjs index 8a4c7b1..7c29844 100644 --- a/lib/providers/mistral.mjs +++ b/lib/providers/mistral.mjs @@ -500,6 +500,20 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl) { const proc = spawnImpl(bin, args, { env, stdio: ['pipe', 'pipe', 'pipe'] }); + // Guard stdin writes against EPIPE (child may close stdin before we finish + // writing — vibe --prompt reads from CLI args, but stdin.end() can still + // surface EPIPE if the process exits before the fd is flushed). + // Port of OCP server.mjs:780. + // Authority: Mistral Vibe CLI https://docs.mistral.ai/mistral-vibe/terminal/quickstart + // observed behaviour — fast-failing spawn closes stdin; Node Writable surfaces EPIPE. + // ALIGNMENT.md Rule 2: no CLI operation changed; guard is a Node stream safety net. + // The typeof guard protects test-injected mock stdin objects that are plain + // objects (not EventEmitters) and do not implement .on(). + if (typeof proc.stdin.on === 'function') { + proc.stdin.on('error', (e) => { + console.error(JSON.stringify({ ts: new Date().toISOString(), level: 'warn', event: 'stdin_write_error', error: e.message })); + }); + } // Close stdin immediately — vibe --prompt reads from args, not stdin. proc.stdin.end();