mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(memory-host-sdk): handle stdout/stderr stream errors in runCliCommand (#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 <noreply@anthropic.com> * 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 #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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
1aeed5ec26
commit
5c4b63964c
2 changed files with 68 additions and 0 deletions
|
|
@ -524,4 +524,52 @@ describe("runCliCommand", () => {
|
|||
});
|
||||
expect(child.kill).not.toHaveBeenCalledWith("SIGKILL");
|
||||
});
|
||||
|
||||
it.each(["stdout", "stderr"] as const)(
|
||||
"rejects when %s stream emits an error",
|
||||
async (streamName) => {
|
||||
const child = createMockChild();
|
||||
spawnMock.mockReturnValueOnce(child);
|
||||
const streamError = new Error(`${streamName} EPIPE`);
|
||||
|
||||
const pending = runCliCommand({
|
||||
commandSummary: "qmd query test",
|
||||
spawnInvocation: { command: "qmd", argv: ["query", "test", "--json"] },
|
||||
env: process.env,
|
||||
cwd: tempDir,
|
||||
maxOutputChars: 10_000,
|
||||
});
|
||||
|
||||
child[streamName].emit("error", streamError);
|
||||
|
||||
await expect(pending).rejects.toMatchObject({
|
||||
message: `qmd query test ${streamName} error: ${streamName} EPIPE`,
|
||||
cause: streamError,
|
||||
});
|
||||
expect(child.kill).toHaveBeenCalledOnce();
|
||||
expect(child.kill).toHaveBeenCalledWith("SIGKILL");
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps the other stream guarded after stdout error", async () => {
|
||||
const child = createMockChild();
|
||||
spawnMock.mockReturnValueOnce(child);
|
||||
|
||||
const pending = runCliCommand({
|
||||
commandSummary: "qmd query test",
|
||||
spawnInvocation: { command: "qmd", argv: ["query", "test", "--json"] },
|
||||
env: process.env,
|
||||
cwd: tempDir,
|
||||
maxOutputChars: 10_000,
|
||||
});
|
||||
|
||||
child.stdout.emit("error", new Error("stdout EPIPE"));
|
||||
|
||||
expect(() => {
|
||||
child.stderr.emit("error", new Error("stderr later"));
|
||||
}).not.toThrow();
|
||||
|
||||
await expect(pending).rejects.toThrow("qmd query test stdout error: stdout EPIPE");
|
||||
expect(child.kill).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -246,6 +246,26 @@ export async function runCliCommand(params: {
|
|||
stderr = next.text;
|
||||
stderrTruncated = stderrTruncated || next.truncated;
|
||||
});
|
||||
|
||||
// Guard stdout/stderr against stream errors (e.g. EPIPE when the
|
||||
// child exits before all pipe data is consumed). Without listeners,
|
||||
// Node.js throws an uncaught exception that crashes the process.
|
||||
for (const streamName of ["stdout", "stderr"] as const) {
|
||||
child[streamName].on("error", (error: Error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
signalQmdProcessTree(child, "SIGKILL");
|
||||
settle(() =>
|
||||
reject(
|
||||
new Error(`${params.commandSummary} ${streamName} error: ${error.message}`, {
|
||||
cause: error,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
child.on("error", (err) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue