From e4c49067607b1583dbd0382741d19eb3d2dc17a7 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Mon, 27 Apr 2026 23:15:13 +0300 Subject: [PATCH] fix: defer Workspace Build output channel creation until first write Constructing TerminalOutputChannel previously called createOutputChannel and show(true) immediately, which forced the Output panel open even on reconnects to an already-running workspace where no build logs would ever be written. The empty channel was disposed shortly after, but the Output panel itself stayed visible. Defer both calls to the first write, so reconnects to a running workspace never create the channel and never pop the panel. --- CHANGELOG.md | 7 +++++++ src/remote/terminalOutputChannel.ts | 20 ++++++++++++------- .../unit/remote/terminalOutputChannel.test.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7a3e63a4..ab29d5747e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ from published versions since it shows up in the VS Code extension changelog tab and is confusing to users. Add it back between releases if needed. --> +## Unreleased + +### Fixed + +- The **Coder: Workspace Build** output channel is no longer created when reconnecting to an + already-running workspace, so the Output panel doesn't pop open empty. + ## [v1.14.4-pre](https://github.com/coder/vscode-coder/releases/tag/v1.14.4-pre) 2026-04-20 ### Added diff --git a/src/remote/terminalOutputChannel.ts b/src/remote/terminalOutputChannel.ts index 40c6c77418..f80f5ca35a 100644 --- a/src/remote/terminalOutputChannel.ts +++ b/src/remote/terminalOutputChannel.ts @@ -1,20 +1,26 @@ import stripAnsi from "strip-ansi"; import * as vscode from "vscode"; -/** Adapts terminal-style output for a VS Code OutputChannel. Strips ANSI escape sequences and carriage returns. */ +/** + * Wraps a VS Code OutputChannel for terminal-style output, stripping ANSI + * escapes and carriage returns. The channel is created lazily on first write + * to avoid surfacing an empty pane in the Output dropdown when nothing is + * ever written. + */ export class TerminalOutputChannel implements vscode.Disposable { - private readonly channel: vscode.OutputChannel; + private channel: vscode.OutputChannel | undefined; - constructor(name: string) { - this.channel = vscode.window.createOutputChannel(name); - this.channel.show(true); - } + constructor(private readonly name: string) {} write(data: string): void { + if (!this.channel) { + this.channel = vscode.window.createOutputChannel(this.name); + this.channel.show(true); + } this.channel.append(stripAnsi(data).replace(/\r/g, "")); } dispose(): void { - this.channel.dispose(); + this.channel?.dispose(); } } diff --git a/test/unit/remote/terminalOutputChannel.test.ts b/test/unit/remote/terminalOutputChannel.test.ts index e8aaf85484..dcb9b88dab 100644 --- a/test/unit/remote/terminalOutputChannel.test.ts +++ b/test/unit/remote/terminalOutputChannel.test.ts @@ -27,4 +27,13 @@ describe("TerminalOutputChannel", () => { ])("%s", (_label, input, expected) => { expect(setup(input).content.join("")).toBe(expected); }); + + it("does not create the channel until first write", () => { + vi.mocked(vscode.window.createOutputChannel).mockClear(); + const channel = new TerminalOutputChannel("test"); + expect(vscode.window.createOutputChannel).not.toHaveBeenCalled(); + + channel.write("hello"); + expect(vscode.window.createOutputChannel).toHaveBeenCalledOnce(); + }); });