Skip to content

Commit 149cd86

Browse files
committed
fix(acpx): handle target stdout stream errors
1 parent 21f2512 commit 149cd86

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

extensions/acpx/src/runtime-internals/mcp-proxy.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ function main() {
118118
};
119119

120120
child.stdin.on("error", exitWithError);
121+
child.stdout.on("error", exitWithError);
121122
process.stdout.on("error", exitWithError);
122123

123124
input.on("line", (line) => {

extensions/acpx/src/runtime-internals/mcp-proxy.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,53 @@ setTimeout(() => {}, 30_000);
238238
expect(stderr).toMatch(/EPIPE|write/i);
239239
expect(stderr).not.toContain("Unhandled 'error' event");
240240
});
241+
242+
it("reports target stdout stream failures without an unhandled stream error", async () => {
243+
const spawnMockPath = await makeTempScript(
244+
"mock-target-stdout-error.cjs",
245+
String.raw`const childProcess = require("node:child_process");
246+
const { EventEmitter } = require("node:events");
247+
const { syncBuiltinESMExports } = require("node:module");
248+
const { PassThrough } = require("node:stream");
249+
250+
childProcess.spawn = () => {
251+
const child = new EventEmitter();
252+
child.stdin = new PassThrough();
253+
child.stdout = new PassThrough();
254+
child.kill = () => {};
255+
256+
process.nextTick(() => {
257+
child.stdout.emit("error", new Error("target stdout failed"));
258+
});
259+
260+
return child;
261+
};
262+
syncBuiltinESMExports();
263+
`,
264+
);
265+
266+
const payload = encodePayload({
267+
targetCommand: `${process.execPath} unused-target.cjs`,
268+
mcpServers: [],
269+
});
270+
271+
const child = spawn(process.execPath, ["--require", spawnMockPath, proxyPath, "--payload", payload], {
272+
stdio: ["pipe", "pipe", "pipe"],
273+
cwd: process.cwd(),
274+
});
275+
276+
let stderr = "";
277+
child.stderr.on("data", (chunk) => {
278+
stderr += String(chunk);
279+
});
280+
child.stdin.end();
281+
282+
const exitCode = await new Promise<number | null>((resolve) => {
283+
child.once("close", (code) => resolve(code));
284+
});
285+
286+
expect(exitCode).toBe(1);
287+
expect(stderr).toContain("target stdout failed");
288+
expect(stderr).not.toContain("Unhandled 'error' event");
289+
});
241290
});

0 commit comments

Comments
 (0)