From 73ac93a9f1be1c55d326905a4bc363e0b52feaec Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 20 Aug 2026 14:47:24 -0400 Subject: [PATCH] Deliver the oversize refusal before hanging up, and pin a POSIX-only test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two left from the platform run, one real. THE DAEMON HUNG UP WITHOUT SAYING WHY, ON macOS ONLY The line-length cap did `socket.write(refusal)` and then `socket.destroy()`. destroy() tears the socket down immediately and discards whatever is still buffered — on Linux the write usually reaches the kernel first and the peer sees "request too large", on macOS it does not and the peer sees an unexplained close. The refusal itself worked on both; only the reason was lost, and only on one platform, which is why nothing noticed until this suite ran there. Now `end(payload, cb)` and destroy from the callback, so the bytes are flushed before teardown, plus a `refused` flag so a peer still pushing the rest of its megabyte is ignored rather than re-buffered. The test that caught it was itself half the problem: it treated its own EPIPE as fatal, when a client's broken pipe is the expected consequence of a server correctly hanging up mid-write. Fixed in the previous commit; this one fixes what that then exposed. AND ONE TEST ASSERTING POSIX BEHAVIOUR WITHOUT SAYING SO `honours backslash escapes outside single quotes` called parseCommandSpec with no platform flag, so it read process.platform and asserted POSIX semantics on Windows — where backslash is a path separator and deliberately is not an escape. It now passes `{ windows: false }`, because that is the branch it is about. Windows went 15 failures to 1 to 0; macOS 2 to 1 to 0. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN --- js/packages/cli/src/agent.ts | 16 ++++++++++++++-- js/packages/cli/test/sinks.test.ts | 10 ++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/js/packages/cli/src/agent.ts b/js/packages/cli/src/agent.ts index fea732d9..c4731f7a 100644 --- a/js/packages/cli/src/agent.ts +++ b/js/packages/cli/src/agent.ts @@ -899,13 +899,25 @@ export class AgentDaemon { this.connections--; }); let buffer = ""; + let refused = false; socket.on("data", (chunk) => { // Without a cap, a peer that never sends a newline grows this string // without bound while every chunk rescans it — gigabytes of RSS and a // pegged event loop, from an unauthenticated connection. + if (refused) return; // already answered; stop buffering this peer if (buffer.length + chunk.length > MAX_LINE_BYTES) { - socket.write(JSON.stringify({ ok: false, error: "request too large" }) + "\n"); - socket.destroy(); + refused = true; + // `end(payload, cb)` and THEN destroy, not `write` followed by an + // immediate `destroy`. destroy() tears the socket down at once and + // discards whatever is still buffered: on Linux the write usually + // reaches the kernel first and the peer sees the refusal, on macOS it + // does not and the peer sees an unexplained hangup. The callback + // fires once the bytes are flushed, so the reason is delivered and + // the connection still closes rather than being held open by a peer + // that keeps sending. + socket.end(JSON.stringify({ ok: false, error: "request too large" }) + "\n", () => { + socket.destroy(); + }); return; } buffer += chunk.toString("utf8"); diff --git a/js/packages/cli/test/sinks.test.ts b/js/packages/cli/test/sinks.test.ts index 971be11b..c3e52b38 100644 --- a/js/packages/cli/test/sinks.test.ts +++ b/js/packages/cli/test/sinks.test.ts @@ -41,9 +41,15 @@ describe("parseCommandSpec", () => { ]); }); + // Explicitly the POSIX branch. Without the flag this reads process.platform + // and asserts POSIX behaviour on Windows, where backslash is a path + // separator and deliberately is not an escape. it("honours backslash escapes outside single quotes", () => { - expect(parseCommandSpec(["cmd a\\ b"])).toEqual(["cmd", ["a b"]]); - expect(parseCommandSpec(["cmd 'a\\ b'"])).toEqual(["cmd", ["a\\ b"]]); + expect(parseCommandSpec(["cmd a\\ b"], { windows: false })).toEqual(["cmd", ["a b"]]); + expect(parseCommandSpec(["cmd 'a\\ b'"], { windows: false })).toEqual([ + "cmd", + ["a\\ b"], + ]); }); it("collapses runs of whitespace", () => {