mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
fix(providers): guard stdin EPIPE on fast-failing CLI spawn (P1 crash-hardening)
Adds `proc.stdin.on('error', ...)` guard in anthropic.mjs, codex.mjs, and
mistral.mjs before any stdin.write() / stdin.end() call. Prevents a
fast-failing CLI (auth error, bad model, oversized prompt) from crashing the
single-process daemon via an unhandled EPIPE on the Node stdin Writable.
The ChildProcess 'error' event fires on the spawned process itself; it does
NOT catch errors on the stdin Writable. The stdin Writable emits its own
'error' event which, without a listener, propagates as an unhandled exception
and crashes the daemon.
Authority (ALIGNMENT.md Rule 1 + Rule 5):
- anthropic: claude CLI v2.1.104 observed behaviour — fast-failing spawn
(e.g. auth error, bad model) 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 only.
Port source: OCP server.mjs:777-784.
- codex: Codex CLI reference https://developers.openai.com/codex/cli/reference
— same mechanism; stdin path via `-` positional documented in reference
§ "Use '-' to pipe the prompt from stdin." ALIGNMENT.md Rule 2 applies.
- mistral: Mistral Vibe CLI https://docs.mistral.ai/mistral-vibe/terminal/quickstart
— vibe --prompt reads from args not stdin, but stdin.end() can still
surface EPIPE if the process exits before the fd is flushed.
ALIGNMENT.md Rule 2: no CLI operation changed.
A `typeof proc.stdin.on === 'function'` guard is included to protect
test-injected mock stdin objects (plain objects without .on()) from
breaking the EventEmitter call.
Co-Authored-By: Claude <claude-sonnet-4-6> <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude <claude-sonnet-4-6> <noreply@anthropic.com>
parent
74e67fdca3
commit
a503441ea3
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user