Skip to content

Commit eb898ac

Browse files
authored
fix(upgrade): remove v prefix from release URLs and work around Bun.write streaming bug (#243)
## Summary Fixes two bugs that prevent `sentry cli upgrade` from working for curl-installed users (CLI-5F): 1. **`v` prefix on release URLs** — `getBinaryDownloadUrl()` and `versionExists()` constructed GitHub release URLs with a `v` prefix (e.g., `/download/v0.10.1/...`), but this repo's tags are un-prefixed (`0.10.1`), causing HTTP 404 errors. 2. **`Bun.write(path, Response)` event-loop bug** — Even with correct URLs, `Bun.write(tempPath, response)` with a large streaming HTTP response body doesn't properly keep the Bun event loop alive. The process exits mid-download with code 0 and no error output. The workaround is to materialise the body first via `await response.arrayBuffer()`, then write the buffer. ## Changes **`src/lib/binary.ts`** - Remove `v` prefix from download URL template in `getBinaryDownloadUrl()` **`src/lib/upgrade.ts`** - Remove `v` prefix from version-exists tag check URL in `versionExists()` - Replace `Bun.write(tempPath, response)` with `response.arrayBuffer()` + `Bun.write(tempPath, body)` to work around the Bun streaming bug **Tests** — Updated URL assertions and mocks across 3 test files to match the un-prefixed tag format. ## How the Bun bug was diagnosed `strace` showed the process actively receiving data from GitHub's CDN, then calling `exit_group(0)` mid-download. Adding debug logging confirmed `Bun.write(path, Response)` starts but its `await` never resolves — the event loop drains and the process exits cleanly. A standalone repro (`await Bun.write(path, await fetch(url))`) hangs indefinitely, but within the CLI's async call stack, the process exits. Materialising the body first with `arrayBuffer()` fixes both cases.
1 parent e0233bd commit eb898ac

File tree

5 files changed

+18
-14
lines changed

5 files changed

+18
-14
lines changed

src/lib/binary.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function getBinaryDownloadUrl(version: string): string {
3939
const arch = process.arch === "arm64" ? "arm64" : "x64";
4040
const suffix = process.platform === "win32" ? ".exe" : "";
4141

42-
return `https://github.com/getsentry/cli/releases/download/v${version}/sentry-${os}-${arch}${suffix}`;
42+
return `https://github.com/getsentry/cli/releases/download/${version}/sentry-${os}-${arch}${suffix}`;
4343
}
4444

4545
/**

src/lib/upgrade.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export async function versionExists(
316316
): Promise<boolean> {
317317
if (method === "curl") {
318318
const response = await fetchWithUpgradeError(
319-
`${GITHUB_RELEASES_URL}/tags/v${version}`,
319+
`${GITHUB_RELEASES_URL}/tags/${version}`,
320320
{ method: "HEAD", headers: getGitHubHeaders() },
321321
"GitHub"
322322
);
@@ -388,8 +388,12 @@ export async function downloadBinaryToTemp(
388388
);
389389
}
390390

391-
// Write to temp file
392-
await Bun.write(tempPath, response);
391+
// Fully consume the response body before writing to disk.
392+
// Bun.write(path, Response) with a large streaming body can exit the
393+
// process before the download completes (Bun event-loop bug).
394+
// Materialising the body first ensures the await keeps the process alive.
395+
const body = await response.arrayBuffer();
396+
await Bun.write(tempPath, body);
393397

394398
// Set executable permission (Unix only)
395399
if (process.platform !== "win32") {

test/commands/cli/upgrade.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,17 @@ function mockGitHubVersion(version: string): void {
101101

102102
// GitHub latest release endpoint — returns JSON with tag_name
103103
if (urlStr.includes("releases/latest")) {
104-
return new Response(JSON.stringify({ tag_name: `v${version}` }), {
104+
return new Response(JSON.stringify({ tag_name: version }), {
105105
status: 200,
106106
headers: { "content-type": "application/json" },
107107
});
108108
}
109109

110-
// GitHub tag check (for versionExists)
111-
if (urlStr.includes("/releases/tag/")) {
112-
const requested = urlStr.split("/releases/tag/v")[1];
110+
// GitHub tag check (for versionExists) — this repo uses un-prefixed tags
111+
if (urlStr.includes("/releases/tags/")) {
112+
const requested = urlStr.split("/releases/tags/")[1];
113113
if (requested === version) {
114-
return new Response(JSON.stringify({ tag_name: `v${version}` }), {
114+
return new Response(JSON.stringify({ tag_name: version }), {
115115
status: 200,
116116
headers: { "content-type": "application/json" },
117117
});

test/lib/binary.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ describe("getBinaryDownloadUrl", () => {
3131
test("builds correct URL for current platform", () => {
3232
const url = getBinaryDownloadUrl("1.0.0");
3333

34-
expect(url).toContain("/v1.0.0/");
34+
expect(url).toContain("/1.0.0/");
3535
expect(url).toStartWith(
36-
"https://github.com/getsentry/cli/releases/download/v"
36+
"https://github.com/getsentry/cli/releases/download/"
3737
);
3838
expect(url).toContain("sentry-");
3939

test/lib/upgrade.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,10 @@ describe("getBinaryDownloadUrl", () => {
437437
test("builds correct URL for current platform", () => {
438438
const url = getBinaryDownloadUrl("1.0.0");
439439

440-
// URL should contain the version with 'v' prefix (GitHub tag format)
441-
expect(url).toContain("/v1.0.0/");
440+
// URL should contain the version without 'v' prefix (this repo's tag format)
441+
expect(url).toContain("/1.0.0/");
442442
expect(url).toStartWith(
443-
"https://github.com/getsentry/cli/releases/download/v"
443+
"https://github.com/getsentry/cli/releases/download/"
444444
);
445445
expect(url).toContain("sentry-");
446446

0 commit comments

Comments
 (0)