Skip to content

Commit 6e8e919

Browse files
authored
fix: prevent binary download failures with compressed server responses (#812)
Request identity encoding and disable axios decompression for all downloads to avoid failures when servers or proxies apply unexpected content encoding. Closes #810, #786
1 parent 80f0c0a commit 6e8e919

File tree

3 files changed

+43
-9
lines changed

3 files changed

+43
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
Windows) instead of plaintext files, when using CLI >= 2.29.0. Falls back to file storage on
99
Linux, older CLIs, or if the keyring write fails. Controlled via the `coder.useKeyring` setting.
1010

11+
### Fixed
12+
13+
- Fixed CLI binary downloads failing when servers or proxies compress responses unexpectedly.
14+
- Clarified CLI download progress notification wording.
15+
1116
## [v1.13.0](https://github.com/coder/vscode-coder/releases/tag/v1.13.0) 2026-03-03
1217

1318
### Added

src/core/cliManager.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,6 @@ export class CliManager {
362362
binSource,
363363
writeStream,
364364
{
365-
"Accept-Encoding": "gzip",
366365
"If-None-Match": `"${etag}"`,
367366
},
368367
onProgress,
@@ -474,17 +473,22 @@ export class CliManager {
474473
signal: controller.signal,
475474
baseURL: baseUrl,
476475
responseType: "stream",
477-
headers,
478-
decompress: true,
476+
headers: {
477+
...headers,
478+
"Accept-Encoding": "identity",
479+
},
480+
decompress: false,
479481
// Ignore all errors so we can catch a 404!
480482
validateStatus: () => true,
481483
});
482484
this.output.info("Got status code", resp.status);
483485

484486
if (resp.status === 200) {
485-
const rawContentLength = resp.headers["content-length"] as unknown;
487+
const rawContentLength = (resp.headers["content-length"] ??
488+
resp.headers["x-original-content-length"]) as unknown;
486489
const contentLength = Number.parseInt(
487490
typeof rawContentLength === "string" ? rawContentLength : "",
491+
10,
488492
);
489493
if (Number.isNaN(contentLength)) {
490494
this.output.warn(
@@ -501,7 +505,7 @@ export class CliManager {
501505
const completed = await vscode.window.withProgress<boolean>(
502506
{
503507
location: vscode.ProgressLocation.Notification,
504-
title: `Downloading ${baseUrl}`,
508+
title: `Downloading Coder CLI for ${baseUrl}`,
505509
cancellable: true,
506510
},
507511
async (progress, token) => {

test/unit/core/cliManager.test.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ describe("CliManager", () => {
378378
expect.objectContaining({
379379
responseType: "stream",
380380
headers: expect.objectContaining({
381-
"Accept-Encoding": "gzip",
381+
"Accept-Encoding": "identity",
382382
"If-None-Match": '""',
383383
}),
384384
}),
@@ -393,7 +393,7 @@ describe("CliManager", () => {
393393
"/custom/path",
394394
expect.objectContaining({
395395
responseType: "stream",
396-
decompress: true,
396+
decompress: false,
397397
validateStatus: expect.any(Function),
398398
}),
399399
);
@@ -528,10 +528,33 @@ describe("CliManager", () => {
528528

529529
it("handles missing content-length", async () => {
530530
withSuccessfulDownload({ headers: {} });
531+
mockProgress.clearProgressReports();
531532
const result = await manager.fetchBinary(mockApi, "test");
532533
expectPathsEqual(result, BINARY_PATH);
533534
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
534-
});
535+
// Without any content-length header, increment should be undefined.
536+
const reports = mockProgress.getProgressReports();
537+
expect(reports).not.toHaveLength(0);
538+
for (const report of reports) {
539+
expect(report).toMatchObject({ increment: undefined });
540+
}
541+
});
542+
543+
it.each(["content-length", "x-original-content-length"])(
544+
"reports progress with %s header",
545+
async (header) => {
546+
withSuccessfulDownload({ headers: { [header]: "1024" } });
547+
mockProgress.clearProgressReports();
548+
const result = await manager.fetchBinary(mockApi, "test");
549+
expectPathsEqual(result, BINARY_PATH);
550+
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
551+
const reports = mockProgress.getProgressReports();
552+
expect(reports).not.toHaveLength(0);
553+
for (const report of reports) {
554+
expect(report).toMatchObject({ increment: expect.any(Number) });
555+
}
556+
},
557+
);
535558
});
536559

537560
describe("Download Progress Tracking", () => {
@@ -543,7 +566,9 @@ describe("CliManager", () => {
543566
withSuccessfulDownload();
544567
await manager.fetchBinary(mockApi, "test");
545568
expect(vscode.window.withProgress).toHaveBeenCalledWith(
546-
expect.objectContaining({ title: `Downloading ${TEST_URL}` }),
569+
expect.objectContaining({
570+
title: `Downloading Coder CLI for ${TEST_URL}`,
571+
}),
547572
expect.any(Function),
548573
);
549574
});

0 commit comments

Comments
 (0)