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 diff --git a/src/core/cliManager.ts b/src/core/cliManager.ts index a1d4f1ae..6f918eb0 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,17 +473,22 @@ export class CliManager { signal: controller.signal, baseURL: baseUrl, responseType: "stream", - headers, - decompress: true, + headers: { + ...headers, + "Accept-Encoding": "identity", + }, + 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 : "", + 10, ); if (Number.isNaN(contentLength)) { this.output.warn( @@ -501,7 +505,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 243ebfc4..2d386244 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", () => { @@ -543,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), ); });