From 0e3cc129002612c11f49e86c40eca7582999bee2 Mon Sep 17 00:00:00 2001 From: skernelx Date: Thu, 2 Apr 2026 19:10:35 +0800 Subject: [PATCH] Fix macOS exec-host JSONL socket deadlock --- src/infra/jsonl-socket.test.ts | 36 ++++++++++++++++++++++++++++++++++ src/infra/jsonl-socket.ts | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/infra/jsonl-socket.test.ts b/src/infra/jsonl-socket.test.ts index 5875b8b7b3c..191ad75ab8f 100644 --- a/src/infra/jsonl-socket.test.ts +++ b/src/infra/jsonl-socket.test.ts @@ -54,6 +54,42 @@ describe.runIf(process.platform !== "win32")("requestJsonlSocket", () => { }); }); + it("half-closes the write side after sending the request line", async () => { + await withTempDir({ prefix: "openclaw-jsonl-socket-" }, async (dir) => { + const socketPath = path.join(dir, "socket.sock"); + const server = net.createServer((socket) => { + let buffer = ""; + socket.on("data", (chunk) => { + buffer += chunk.toString("utf8"); + }); + socket.on("end", () => { + expect(buffer).toBe('{"hello":"world"}\n'); + socket.end('{"type":"done","value":7}\n'); + }); + }); + const listening = await listenOnSocket(server, socketPath); + if (!listening) { + return; + } + + try { + await expect( + requestJsonlSocket({ + socketPath, + payload: '{"hello":"world"}', + timeoutMs: 500, + accept: (msg) => { + const value = msg as { type?: string; value?: number }; + return value.type === "done" ? (value.value ?? null) : undefined; + }, + }), + ).resolves.toBe(7); + } finally { + server.close(); + } + }); + }); + it("returns null on timeout and on socket errors", async () => { await withTempDir({ prefix: "openclaw-jsonl-socket-" }, async (dir) => { const socketPath = path.join(dir, "socket.sock"); diff --git a/src/infra/jsonl-socket.ts b/src/infra/jsonl-socket.ts index 7aeed049347..f21127a3f57 100644 --- a/src/infra/jsonl-socket.ts +++ b/src/infra/jsonl-socket.ts @@ -30,7 +30,8 @@ export async function requestJsonlSocket(params: { client.on("error", () => finish(null)); client.connect(socketPath, () => { - client.write(`${payload}\n`); + // The macOS exec host can wait for EOF before responding, so finish writing. + client.end(`${payload}\n`); }); client.on("data", (data) => { buffer += data.toString("utf8");