Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions src/core/cliManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@ export class CliManager {
binSource,
writeStream,
{
"Accept-Encoding": "gzip",
"If-None-Match": `"${etag}"`,
},
onProgress,
Expand Down Expand Up @@ -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(
Expand All @@ -501,7 +505,7 @@ export class CliManager {
const completed = await vscode.window.withProgress<boolean>(
{
location: vscode.ProgressLocation.Notification,
title: `Downloading ${baseUrl}`,
title: `Downloading Coder CLI for ${baseUrl}`,
cancellable: true,
},
async (progress, token) => {
Expand Down
33 changes: 29 additions & 4 deletions test/unit/core/cliManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe("CliManager", () => {
expect.objectContaining({
responseType: "stream",
headers: expect.objectContaining({
"Accept-Encoding": "gzip",
"Accept-Encoding": "identity",
"If-None-Match": '""',
}),
}),
Expand All @@ -393,7 +393,7 @@ describe("CliManager", () => {
"/custom/path",
expect.objectContaining({
responseType: "stream",
decompress: true,
decompress: false,
validateStatus: expect.any(Function),
}),
);
Expand Down Expand Up @@ -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", () => {
Expand All @@ -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),
);
});
Expand Down