From 15727ad77cbd14cc553f78cbf9efc1220889b89a Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Mon, 2 Mar 2026 02:24:15 +0300 Subject: [PATCH 1/4] fix: prevent binary download failures with compressed server responses Request identity encoding and disable axios decompression for all downloads to avoid failures when servers or proxies apply unexpected content encoding. Fall back to x-original-content-length header for progress tracking when content-length is absent. --- src/core/cliManager.ts | 11 +++++++---- test/unit/core/cliManager.test.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/core/cliManager.ts b/src/core/cliManager.ts index a1d4f1ae..a3a39ddc 100644 --- a/src/core/cliManager.ts +++ b/src/core/cliManager.ts @@ -362,7 +362,6 @@ export class CliManager { binSource, writeStream, { - "Accept-Encoding": "gzip", "If-None-Match": `"${etag}"`, }, onProgress, @@ -474,15 +473,19 @@ export class CliManager { signal: controller.signal, baseURL: baseUrl, responseType: "stream", - headers, - decompress: true, + headers: { + "Accept-Encoding": "identity", + ...headers, + }, + decompress: false, // Ignore all errors so we can catch a 404! validateStatus: () => true, }); this.output.info("Got status code", resp.status); if (resp.status === 200) { - const rawContentLength = resp.headers["content-length"] as unknown; + const rawContentLength = (resp.headers["content-length"] ?? + resp.headers["x-original-content-length"]) as unknown; const contentLength = Number.parseInt( typeof rawContentLength === "string" ? rawContentLength : "", ); diff --git a/test/unit/core/cliManager.test.ts b/test/unit/core/cliManager.test.ts index 243ebfc4..132e2c89 100644 --- a/test/unit/core/cliManager.test.ts +++ b/test/unit/core/cliManager.test.ts @@ -378,7 +378,7 @@ describe("CliManager", () => { expect.objectContaining({ responseType: "stream", headers: expect.objectContaining({ - "Accept-Encoding": "gzip", + "Accept-Encoding": "identity", "If-None-Match": '""', }), }), @@ -393,7 +393,7 @@ describe("CliManager", () => { "/custom/path", expect.objectContaining({ responseType: "stream", - decompress: true, + decompress: false, validateStatus: expect.any(Function), }), ); @@ -528,10 +528,33 @@ describe("CliManager", () => { it("handles missing content-length", async () => { withSuccessfulDownload({ headers: {} }); + mockProgress.clearProgressReports(); const result = await manager.fetchBinary(mockApi, "test"); expectPathsEqual(result, BINARY_PATH); expect(memfs.existsSync(BINARY_PATH)).toBe(true); - }); + // Without any content-length header, increment should be undefined. + const reports = mockProgress.getProgressReports(); + expect(reports).not.toHaveLength(0); + for (const report of reports) { + expect(report).toMatchObject({ increment: undefined }); + } + }); + + it.each(["content-length", "x-original-content-length"])( + "reports progress with %s header", + async (header) => { + withSuccessfulDownload({ headers: { [header]: "1024" } }); + mockProgress.clearProgressReports(); + const result = await manager.fetchBinary(mockApi, "test"); + expectPathsEqual(result, BINARY_PATH); + expect(memfs.existsSync(BINARY_PATH)).toBe(true); + const reports = mockProgress.getProgressReports(); + expect(reports).not.toHaveLength(0); + for (const report of reports) { + expect(report).toMatchObject({ increment: expect.any(Number) }); + } + }, + ); }); describe("Download Progress Tracking", () => { From aedf886fbffd6097c897fdb246779fa2c224297c Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Wed, 4 Mar 2026 14:50:32 +0300 Subject: [PATCH 2/4] fix: clarify CLI download progress notification wording Fixes #786 --- src/core/cliManager.ts | 2 +- test/unit/core/cliManager.test.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/cliManager.ts b/src/core/cliManager.ts index a3a39ddc..7a277b7a 100644 --- a/src/core/cliManager.ts +++ b/src/core/cliManager.ts @@ -504,7 +504,7 @@ export class CliManager { const completed = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: `Downloading ${baseUrl}`, + title: `Downloading Coder CLI for ${baseUrl}`, cancellable: true, }, async (progress, token) => { diff --git a/test/unit/core/cliManager.test.ts b/test/unit/core/cliManager.test.ts index 132e2c89..2d386244 100644 --- a/test/unit/core/cliManager.test.ts +++ b/test/unit/core/cliManager.test.ts @@ -566,7 +566,9 @@ describe("CliManager", () => { withSuccessfulDownload(); await manager.fetchBinary(mockApi, "test"); expect(vscode.window.withProgress).toHaveBeenCalledWith( - expect.objectContaining({ title: `Downloading ${TEST_URL}` }), + expect.objectContaining({ + title: `Downloading Coder CLI for ${TEST_URL}`, + }), expect.any(Function), ); }); From 858a4e2c73b1b4d64fd961898e95c309d0b92b52 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Wed, 4 Mar 2026 17:09:04 +0300 Subject: [PATCH 3/4] Address review comments --- src/core/cliManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/cliManager.ts b/src/core/cliManager.ts index 7a277b7a..6f918eb0 100644 --- a/src/core/cliManager.ts +++ b/src/core/cliManager.ts @@ -474,8 +474,8 @@ export class CliManager { baseURL: baseUrl, responseType: "stream", headers: { - "Accept-Encoding": "identity", ...headers, + "Accept-Encoding": "identity", }, decompress: false, // Ignore all errors so we can catch a 404! @@ -488,6 +488,7 @@ export class CliManager { resp.headers["x-original-content-length"]) as unknown; const contentLength = Number.parseInt( typeof rawContentLength === "string" ? rawContentLength : "", + 10, ); if (Number.isNaN(contentLength)) { this.output.warn( From e31b1371df9144b3e5aacdbbbd17b0431ba38c94 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Wed, 4 Mar 2026 17:11:55 +0300 Subject: [PATCH 4/4] Added CHANGELOG.md entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4071d67..afe8ed59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Windows) instead of plaintext files, when using CLI >= 2.29.0. Falls back to file storage on Linux, older CLIs, or if the keyring write fails. Controlled via the `coder.useKeyring` setting. +### Fixed + +- Fixed CLI binary downloads failing when servers or proxies compress responses unexpectedly. +- Clarified CLI download progress notification wording. + ## [v1.13.0](https://github.com/coder/vscode-coder/releases/tag/v1.13.0) 2026-03-03 ### Added