From be6c0c0c72be84a5c0b0b37f6f5d2d848a3608f0 Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Mon, 15 Jun 2026 22:48:17 +0800 Subject: [PATCH] fix(install): don't hard-fail when the GitHub releases API blips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported on #930: a transient 504 from api.github.com/.../releases/latest (or the 60/hr/IP unauthenticated rate limit) aborted the whole install with "Failed to fetch version information" — even though the download itself uses releases/latest/download/, which GitHub resolves server-side with no API call. The API response only feeds the version-string display and the already-installed short-circuit. Both installers now, in the latest path: - retry the API call up to 3x with linear backoff (bash uses curl --fail so a 504 retries instead of parsing an error body); - on continued failure, print a muted notice and proceed to install latest anyway (version string shown as "latest"); - only short-circuit as "already installed" on a real version match — never treat empty==empty (unresolved version + unreadable binary) as installed. Pinned-version installs (-Version / --version) are unchanged: a genuine 404 still hard-fails. Tests: version-fetch-resilience.test.ts pins the retry + graceful-degrade behavior in both installers. bash -n clean; install.ps1 parses clean and the Pester suite (6/6) still passes on PowerShell 7.6.2. Co-Authored-By: Claude Opus 4.8 (1M context) --- install | 30 ++++++++---- install.ps1 | 24 ++++++---- .../install/version-fetch-resilience.test.ts | 46 +++++++++++++++++++ 3 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 packages/opencode/test/install/version-fetch-resilience.test.ts diff --git a/install b/install index 2cf359ba0..5d9eab931 100755 --- a/install +++ b/install @@ -205,11 +205,20 @@ else if [ -z "$requested_version" ]; then url="https://github.com/AltimateAI/altimate-code/releases/latest/download/$filename" - specific_version=$(curl -s https://api.github.com/repos/AltimateAI/altimate-code/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') - - if [[ $? -ne 0 || -z "$specific_version" ]]; then - echo -e "${RED}Failed to fetch version information${NC}" - exit 1 + # The download above resolves "latest" server-side, so this API call only + # feeds the version display and the already-installed short-circuit. A + # transient api.github.com blip or the unauthenticated rate limit + # (60/hr/IP) must NOT abort the install — retry a few times with --fail + # (so a 504 retries instead of parsing an error body), then proceed + # without the version string. + specific_version="" + for attempt in 1 2 3; do + specific_version=$(curl -fsSL https://api.github.com/repos/AltimateAI/altimate-code/releases/latest 2>/dev/null | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') + [ -n "$specific_version" ] && break + [ "$attempt" -lt 3 ] && sleep "$attempt" + done + if [ -z "$specific_version" ]; then + echo -e "${MUTED}Could not resolve the latest version from GitHub (API unavailable) — installing the latest release anyway.${NC}" fi else # Strip leading 'v' if present @@ -255,11 +264,14 @@ check_version() { if [ -n "$probe" ]; then installed_version=$("$probe" --version 2>/dev/null || echo "") - if [[ "$installed_version" != "$specific_version" ]]; then - print_message info "${MUTED}Installed version: ${NC}$installed_version." - else + # Only short-circuit on a real version match. When the latest version + # couldn't be resolved (API unavailable → specific_version empty), never + # treat an empty==empty as "already installed" — fall through and reinstall. + if [ -n "$specific_version" ] && [[ "$installed_version" == "$specific_version" ]]; then print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed${NC}" exit 0 + elif [ -n "$installed_version" ]; then + print_message info "${MUTED}Installed version: ${NC}$installed_version." fi fi } @@ -357,7 +369,7 @@ download_with_progress() { } download_and_install() { - print_message info "\n${MUTED}Installing ${NC}altimate ${MUTED}version: ${NC}$specific_version" + print_message info "\n${MUTED}Installing ${NC}altimate ${MUTED}version: ${NC}${specific_version:-latest}" local tmp_dir="${TMPDIR:-/tmp}/altimate_install_$$" mkdir -p "$tmp_dir" diff --git a/install.ps1 b/install.ps1 index bcab7223f..749947270 100644 --- a/install.ps1 +++ b/install.ps1 @@ -112,16 +112,22 @@ function Test-Avx2 { # --------------------------------------------------------------------------- if ([string]::IsNullOrWhiteSpace($Version)) { $useLatest = $true - try { - $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/AltimateAI/altimate-code/releases/latest" -Headers @{ "User-Agent" = "altimate-install" } - $specificVersion = ($rel.tag_name -replace '^v', '') - } catch { - Write-Err "Failed to fetch version information" - exit 1 + # The download below resolves "latest" server-side (releases/latest/download), + # so this API call only feeds the version-string display and the + # already-installed short-circuit. A transient api.github.com blip or the + # unauthenticated rate limit (60/hr/IP) must NOT abort the install — retry a + # few times, then proceed without the version string. + $specificVersion = "" + for ($attempt = 1; $attempt -le 3; $attempt++) { + try { + $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/AltimateAI/altimate-code/releases/latest" -Headers @{ "User-Agent" = "altimate-install" } + $specificVersion = ($rel.tag_name -replace '^v', '') + if (-not [string]::IsNullOrWhiteSpace($specificVersion)) { break } + } catch {} + if ($attempt -lt 3) { Start-Sleep -Seconds $attempt } } if ([string]::IsNullOrWhiteSpace($specificVersion)) { - Write-Err "Failed to fetch version information" - exit 1 + Write-Muted "Could not resolve the latest version from GitHub (API unavailable) — installing the latest release anyway." } } else { $useLatest = $false @@ -177,7 +183,7 @@ function Install-Target { } Write-Host "" - Write-Host "Installing $App version: $specificVersion" + Write-Host "Installing $App version: $(if ($specificVersion) { $specificVersion } else { 'latest' })" $tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) "altimate_install_$PID" New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null diff --git a/packages/opencode/test/install/version-fetch-resilience.test.ts b/packages/opencode/test/install/version-fetch-resilience.test.ts new file mode 100644 index 000000000..4e00fd2ef --- /dev/null +++ b/packages/opencode/test/install/version-fetch-resilience.test.ts @@ -0,0 +1,46 @@ +/** + * Latest-version resolution must be resilient, in BOTH installers. + * + * The `latest` install path hits api.github.com/.../releases/latest only for the + * version-string display + the already-installed short-circuit — the download + * itself uses releases/latest/download/ (server-side latest). A transient + * 504 or the 60/hr/IP unauthenticated rate limit must NOT abort the install: + * retry a few times, then degrade gracefully and install latest anyway. + */ +import { describe, test, expect } from "bun:test" +import { readFileSync } from "node:fs" +import { join } from "node:path" + +const REPO_ROOT = join(import.meta.dir, "../../../..") +const BASH = readFileSync(join(REPO_ROOT, "install"), "utf-8") +const PS1 = readFileSync(join(REPO_ROOT, "install.ps1"), "utf-8") + +describe("bash installer — latest-version fetch is non-fatal", () => { + test("retries the releases/latest API call", () => { + expect(BASH).toContain("for attempt in 1 2 3") + // --fail so a 504 errors out (and retries) instead of parsing an error body. + expect(BASH).toContain("curl -fsSL https://api.github.com") + }) + + test("degrades gracefully instead of exiting on API failure", () => { + expect(BASH).toContain("installing the latest release anyway") + // The old fatal hard-fail must be gone from the latest path. + expect(BASH).not.toContain("Failed to fetch version information") + }) + + test("only short-circuits as already-installed on a real version match", () => { + expect(BASH).toContain('[ -n "$specific_version" ] && [[ "$installed_version" == "$specific_version" ]]') + }) +}) + +describe("PowerShell installer — latest-version fetch is non-fatal", () => { + test("retries the releases/latest API call", () => { + expect(PS1).toContain("for ($attempt = 1; $attempt -le 3; $attempt++)") + }) + + test("degrades gracefully instead of exiting on API failure", () => { + expect(PS1).toContain("installing the latest release anyway") + // The old fatal hard-fail must be gone. + expect(PS1).not.toContain("Failed to fetch version information") + }) +})