fix(memory-host-sdk): handle stdout/stderr stream errors in runCliCommand#101402
Conversation
4c2893d to
a5c46f6
Compare
|
Codex review: needs maintainer review before merge. Reviewed July 7, 2026, 3:30 AM ET / 07:30 UTC. Summary PR surface: Source +20, Tests +48. Total +68 across 2 files. Reproducibility: yes. for the underlying source and stream-contract path: current main reads QMD stdout/stderr without Review metrics: none identified. Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Land the focused stream-error guard after required CI completes, keeping the fix inside the QMD process wrapper and leaving release changelog updates to the release flow. Do we have a high-confidence way to reproduce the issue? Yes for the underlying source and stream-contract path: current main reads QMD stdout/stderr without Is this the best way to solve the issue? Yes. Handling stdout/stderr errors in AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 41c0540bdcab. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +20, Tests +48. Total +68 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (3 earlier review cycles) |
…mand Register error handlers on stdout and stderr streams in runCliCommand() to prevent uncaught exceptions when a pipe breaks (e.g. EPIPE after child process exit). Without these listeners, Node.js throws an uncaught exception that crashes the process. Co-Authored-By: Claude <[email protected]>
Refactor the inline stream error handling into a reusable listenForChildOutputErrors helper, matching the pattern established in the merged openclaw#101370 (agent-core). The helper registers error listeners on stdout/stderr via a shared loop, keeping the pattern consistent across the codebase. Co-Authored-By: Claude <[email protected]>
a5c46f6 to
81e6dcd
Compare
|
@clawsweeper re-review — refactored to use reusable |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Land-ready at exact head What changed:
Proof:
Known proof gaps: none for this bounded subprocess failure path. |
|
Merged via squash.
|
…mand (openclaw#101402) * fix(memory-host-sdk): handle stdout/stderr stream errors in runCliCommand Register error handlers on stdout and stderr streams in runCliCommand() to prevent uncaught exceptions when a pipe breaks (e.g. EPIPE after child process exit). Without these listeners, Node.js throws an uncaught exception that crashes the process. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): extract listenForChildOutputErrors helper Refactor the inline stream error handling into a reusable listenForChildOutputErrors helper, matching the pattern established in the merged openclaw#101370 (agent-core). The helper registers error listeners on stdout/stderr via a shared loop, keeping the pattern consistent across the codebase. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): harden qmd stream failures * fix(memory-host-sdk): harden qmd stream failures * docs(changelog): note qmd stream hardening * chore: keep release changelog out of contributor PR --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
|
Thank you @steipete for the thorough review and squash merge! 🙏 |
…mand (openclaw#101402) * fix(memory-host-sdk): handle stdout/stderr stream errors in runCliCommand Register error handlers on stdout and stderr streams in runCliCommand() to prevent uncaught exceptions when a pipe breaks (e.g. EPIPE after child process exit). Without these listeners, Node.js throws an uncaught exception that crashes the process. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): extract listenForChildOutputErrors helper Refactor the inline stream error handling into a reusable listenForChildOutputErrors helper, matching the pattern established in the merged openclaw#101370 (agent-core). The helper registers error listeners on stdout/stderr via a shared loop, keeping the pattern consistent across the codebase. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): harden qmd stream failures * fix(memory-host-sdk): harden qmd stream failures * docs(changelog): note qmd stream hardening * chore: keep release changelog out of contributor PR --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…mand (openclaw#101402) * fix(memory-host-sdk): handle stdout/stderr stream errors in runCliCommand Register error handlers on stdout and stderr streams in runCliCommand() to prevent uncaught exceptions when a pipe breaks (e.g. EPIPE after child process exit). Without these listeners, Node.js throws an uncaught exception that crashes the process. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): extract listenForChildOutputErrors helper Refactor the inline stream error handling into a reusable listenForChildOutputErrors helper, matching the pattern established in the merged openclaw#101370 (agent-core). The helper registers error listeners on stdout/stderr via a shared loop, keeping the pattern consistent across the codebase. Co-Authored-By: Claude <[email protected]> * fix(memory-host-sdk): harden qmd stream failures * fix(memory-host-sdk): harden qmd stream failures * docs(changelog): note qmd stream hardening * chore: keep release changelog out of contributor PR --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
Summary
Problem:
runCliCommand()inpackages/memory-host-sdk/src/host/qmd-process.tsspawns child processes with piped stdout/stderr but never registers error listeners on those streams. Per Node.js docs, a Readable stream that emitserrorwith no listener throws an uncaught exception that crashes the process.Solution: Add stream error handlers on
child.stdoutandchild.stderrthat kill the child process and reject the promise with a descriptive error. Follows the identical pattern already merged in docker.ts (#101031) and applied in agent-core (#101370).What changed:
qmd-process.ts—runCliCommand()(+15 lines): stream error guard.qmd-process.test.ts(+42 lines): two new test cases.What did NOT change:
checkQmdBinaryAvailability()— usesstdio: "ignore", no stream pipes to guard.What Problem This Solves
Problem: When a child process spawned by
runCliCommand()exits or is killed while pipes still have buffered data, the OS closes the pipe and the stream emitserror: EPIPE. With no error listener, Node.js throws an uncaught exception.Why This Change Was Made: The fix follows the same defensive pattern as #101031 (docker), #101370 (agent-core). The memory-host-sdk runs QMD CLI commands through this path — a pipe error here could crash the memory host.
User Impact: Normal operation unchanged. Prevents rare crash during QMD command execution.
Root Cause
Trigger condition: Child process killed (timeout/abort) while pipe data is still draining → EPIPE → no listener → uncaught exception.
Affected scope:
packages/memory-host-sdk/src/host/qmd-process.ts—runCliCommand().Linked context
runCliCommand()— stream error handlers on stdout + stderrcheckQmdBinaryAvailability()— stdio:"ignore"Real behavior proof
Behavior addressed: Uncaught EPIPE on child stdout/stderr crashes the process; fix registers error listeners.
Real environment tested: Linux x86_64, Node.js 24
Exact steps or command run after this patch:
After-fix evidence:
Observed result after the fix: Source has error handlers on both pipes. Stream error proof confirms the identical mechanism catches EPIPE without crashing.
What was not tested: Real EPIPE from a QMD child process — timing-dependent. Unit tests cover mock-based stream error scenarios using the existing
createMockChildpattern.Maintainer exact-head proof
The patched
runCliCommandpath and its real process-tree lifecycle test were run from immutable head914a00cf7142aec7cd030913ae314dc1a8e6cf2fin a sanitized, public-network AWS Crabbox with no instance role, no Tailscale, and no hydrated credentials.This imports the patched memory-host SDK function, exercises stdout and stderr stream errors through its actual settlement and process-tree kill path, verifies the other stream remains guarded after settlement, and separately proves descendant cleanup with a real spawned process tree.
Tests and validation
Two new test cases in
qmd-process.test.ts:rejects when stdout/stderr stream emits an error— param test for both streamskeeps the other stream guarded after stdout error— late error must not throwRisk checklist
Merge-risk: Low. Additive change only. Stream errors that previously crashed are now caught and reported.
Current review state
AI-assisted.