From 3d575eec6f41a57554710fa005bd4db951aee6d3 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:32:24 +0000 Subject: [PATCH] fix: re-check cancellation after postToolUse hook to avoid closed-stream write (fixes #321951) (#321959) fix: re-check cancellation after postToolUse hook to avoid closed stream write (fixes #321951) The postToolUse hook can run for a long time because it spawns external, user-configured commands. If the chat request is cancelled while the hook runs, the response stream is closed by the time the hook resolves, and writing hook progress to it throws an unhandled "Response stream has been closed" error. The prior fix (cdf17eb2, #319011) only checked cancellation before invoking the hook, leaving the cancellation-during-hook window open. Re-check the cancellation token in executePostToolUseHook after the await and skip result processing when cancelled, since a cancelled turn never consumes the result. Co-authored-by: vs-code-engineering[bot] <122617954+vs-code-engineering[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/extension/chat/vscode-node/chatHookService.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extensions/copilot/src/extension/chat/vscode-node/chatHookService.ts b/extensions/copilot/src/extension/chat/vscode-node/chatHookService.ts index a35158b37d2..b6b9501a871 100644 --- a/extensions/copilot/src/extension/chat/vscode-node/chatHookService.ts +++ b/extensions/copilot/src/extension/chat/vscode-node/chatHookService.ts @@ -501,6 +501,16 @@ export class ChatHookService implements IChatHookService { token ); + // Running the hook can take a long time because it spawns external, user-configured + // commands. If the request was cancelled while the hook ran, the response stream is + // already closed and writing hook progress to it throws "Response stream has been + // closed". The caller only checks cancellation before invoking the hook, so re-check + // here after the await and skip result processing - a cancelled turn never consumes + // the result anyway. + if (token?.isCancellationRequested) { + return undefined; + } + if (results.length === 0) { return undefined; }