From 6815c8ff1b484df383a8bc9fec47b5f55a752af5 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Wed, 25 Feb 2026 01:37:35 +0100 Subject: [PATCH] fix(js-sdk): stop waiting for stream close after command end event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On receiving the "end" event in iterateEvents(), call handleDisconnect() to abort the transport controller, then return from the generator to stop blocking on the stream. In handleEvents(), ignore iteration errors when a result has already been captured since the error is from the expected abort. After the generator exits, fire a .next() call on the events stream to trigger connectrpc's deadline timer cleanup — on platforms where abort propagates to the body reader (Deno) this clears the timer immediately; on Node.js (nodejs/undici#1940) it settles when the server closes the stream. --- .../src/sandbox/commands/commandHandle.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/src/sandbox/commands/commandHandle.ts b/packages/js-sdk/src/sandbox/commands/commandHandle.ts index 67852a900c..eed42fd4fe 100644 --- a/packages/js-sdk/src/sandbox/commands/commandHandle.ts +++ b/packages/js-sdk/src/sandbox/commands/commandHandle.ts @@ -216,7 +216,8 @@ export class CommandHandle stdout: this.stdout, stderr: this.stderr, } - break + this.handleDisconnect() + return } // TODO: Handle empty events like in python SDK } @@ -234,7 +235,19 @@ export class CommandHandle } } } catch (e) { - this.iterationError = handleRpcError(e) + if (!this.result) { + this.iterationError = handleRpcError(e) + } + } + // Trigger connectrpc's deadline timer cleanup by forcing one more + // .next() call on the events stream. On platforms where abort propagates + // to the body reader (Deno), this rejects and clears the timer via + // connectrpc's abort() callback. On platforms where it doesn't (Node.js + // per nodejs/undici#1940), this settles when the server closes the stream. + if (this.result) { + this.events[Symbol.asyncIterator]() + .next() + .catch(() => {}) } } }