From e63c3bebb60441d362f0aa59cfc36802ddb39cca Mon Sep 17 00:00:00 2001 From: wan9chi Date: Mon, 9 Nov 2026 19:52:12 +0800 Subject: [PATCH] test(cache): keep the backend's early answer to an upload in the e2e proxy The remote cache e2e proxy streamed each request body to the backend as it arrived. The Worker rejects an upload without a token by its headers, answering 401 before it reads the body, and then closes the connection, which resets it if body bytes are still unread. On a slow runner, a later piece of the body could reach the proxy after that reset but before the proxy read the 401. Writing it failed with EPIPE, Node destroyed the socket with the 401 unread, and the proxy answered 502, so `remote_cache::upload_without_token` failed on macOS CI. The proxy now reads the whole request before forwarding it, so the body goes out with the headers before the backend can answer. Co-Authored-By: Claude Opus 5.5 --- packages/tools/src/remote-cache/daemon.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/tools/src/remote-cache/daemon.ts b/packages/tools/src/remote-cache/daemon.ts index 876c50fa5..709d867a2 100644 --- a/packages/tools/src/remote-cache/daemon.ts +++ b/packages/tools/src/remote-cache/daemon.ts @@ -147,7 +147,8 @@ function pass( ); upstream.on('error', (error) => { // The Worker rejects an upload by its headers, answering before it reads - // the body, and then closes the connection, so sending the rest fails. + // the body, and then closes the connection, which can reset it after the + // answer. if (clientGone || backendReply?.complete) return; anomalies.push(`${label} failed: ${error.message}`); if (response.headersSent) response.destroy(); @@ -159,7 +160,13 @@ function pass( clientGone = true; upstream.destroy(); }); - request.pipe(upstream); + // Send the body once it has all arrived, so it goes out with the headers + // before the Worker can answer. Sending part of it after a rejection fails + // if the connection was reset, and that can lose the answer before it's + // read. + const body: Buffer[] = []; + request.on('data', (chunk: Buffer) => body.push(chunk)); + request.on('end', () => upstream.end(Buffer.concat(body))); } const control = createServer((request, response) => {