From dcaab0475b248277577eedb37b43b5d9cb1dcb83 Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Mon, 15 Jun 2026 22:48:17 +0800 Subject: [PATCH 1/4] 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") + }) +}) From d23fc1a51ef105fde359e919608af5d5353219d0 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 16:26:09 +0800 Subject: [PATCH 2/4] fix(install): address latest-version-fetch review - install: append `|| true` to the retry's curl|sed assignment. Under `set -euo pipefail` a failing `curl --fail` propagated through the pipeline and aborted the script at attempt 1, before the loop could retry or degrade (sahrizvi; reproduced: exit 22 without the fix, all 3 attempts + degrade with it). Also add `--max-time 10` to bound a dead-air socket. - install.ps1: reset $specificVersion to $null (not "") on the degrade path, so the already-installed short-circuit can't false-match "" -eq "" when the version probe of a missing/corrupt binary also yields "" (dev-punia, sahrizvi). - install.ps1: add -TimeoutSec 10 to Invoke-RestMethod (defaults to 100s on PS 5.1, unbounded on PS 7+) to bound retries on dead air (sahrizvi). - tests: TS guards for `|| true`, --max-time/-TimeoutSec, and the $null reset. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- install | 8 ++++++- install.ps1 | 12 +++++++++- .../install/version-fetch-resilience.test.ts | 23 ++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/install b/install index 5d9eab931..8bbf4a6e7 100755 --- a/install +++ b/install @@ -211,9 +211,15 @@ else # (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. + # + # --max-time 10 bounds a dead-air socket (curl's default has no transfer + # cap), and the trailing `|| true` is load-bearing: under `set -euo + # pipefail`, a failing `curl --fail` propagates through the pipeline and + # the assignment, so `set -e` would abort the script before the loop can + # retry or degrade. `|| true` lets the failure resolve to an empty 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') + specific_version=$(curl -fsSL --max-time 10 https://api.github.com/repos/AltimateAI/altimate-code/releases/latest 2>/dev/null | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p' || true) [ -n "$specific_version" ] && break [ "$attempt" -lt 3 ] && sleep "$attempt" done diff --git a/install.ps1 b/install.ps1 index 749947270..9f0cc0972 100644 --- a/install.ps1 +++ b/install.ps1 @@ -120,7 +120,10 @@ if ([string]::IsNullOrWhiteSpace($Version)) { $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" } + # -TimeoutSec 10 bounds a stuck socket: Invoke-RestMethod defaults to 100s + # on PS 5.1 and is effectively unbounded on PS 7+, so without it three + # back-to-back retries on dead air could freeze for minutes. + $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/AltimateAI/altimate-code/releases/latest" -Headers @{ "User-Agent" = "altimate-install" } -TimeoutSec 10 $specificVersion = ($rel.tag_name -replace '^v', '') if (-not [string]::IsNullOrWhiteSpace($specificVersion)) { break } } catch {} @@ -128,6 +131,13 @@ if ([string]::IsNullOrWhiteSpace($Version)) { } if ([string]::IsNullOrWhiteSpace($specificVersion)) { Write-Muted "Could not resolve the latest version from GitHub (API unavailable) — installing the latest release anyway." + # Reset to $null (not ""): the already-installed short-circuit below compares + # $installedVersion -eq $specificVersion. If the version probe of a missing or + # corrupt binary also yields "", an "" -eq "" match would falsely report + # "already installed" and skip the reinstall. $null -eq "" is $false, so the + # comparison correctly falls through; the banner still shows "latest" because + # if ($specificVersion) treats $null as falsy. + $specificVersion = $null } } else { $useLatest = $false diff --git a/packages/opencode/test/install/version-fetch-resilience.test.ts b/packages/opencode/test/install/version-fetch-resilience.test.ts index 4e00fd2ef..6f492fc2b 100644 --- a/packages/opencode/test/install/version-fetch-resilience.test.ts +++ b/packages/opencode/test/install/version-fetch-resilience.test.ts @@ -19,7 +19,18 @@ 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") + expect(BASH).toContain("curl -fsSL --max-time 10 https://api.github.com") + }) + + test("the retry assignment absorbs curl failure so set -e can't abort it", () => { + // Under `set -euo pipefail`, a failing `curl --fail` propagates through the + // pipeline + assignment and aborts the script before the loop can retry or + // degrade. The trailing `|| true` keeps the retry loop alive. + expect(BASH).toMatch(/curl -fsSL --max-time 10 https:\/\/api\.github\.com[^\n]*\|\| true/) + }) + + test("bounds the API call with a transfer timeout", () => { + expect(BASH).toContain("--max-time 10") }) test("degrades gracefully instead of exiting on API failure", () => { @@ -38,9 +49,19 @@ describe("PowerShell installer — latest-version fetch is non-fatal", () => { expect(PS1).toContain("for ($attempt = 1; $attempt -le 3; $attempt++)") }) + test("bounds the API call with a request timeout", () => { + expect(PS1).toContain("-TimeoutSec 10") + }) + 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") }) + + test("resets the unresolved version to $null so empty==empty can't false-match", () => { + // $installedVersion -eq $specificVersion with both "" would falsely report + // "already installed" for a missing/corrupt binary; $null -eq "" is $false. + expect(PS1).toContain("$specificVersion = $null") + }) }) From 5f9cf45a186b52def6f168254c8b8f61dc0f7f91 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 17:07:59 +0800 Subject: [PATCH 3/4] fix(install.ps1): ASCII-only so it parses on Windows PowerShell 5.1 install.ps1 had no BOM and used a few non-ASCII characters (em dash, ellipsis, right arrow) in comments and messages. Windows PowerShell 5.1 - the default shell on Windows 10 and preinstalled on Windows 11 - reads a BOM-less file as the system ANSI codepage, not UTF-8, so those multi-byte characters corrupt the token stream and the whole script fails to parse (verified on real PS 5.1). This is a pre-existing issue (the characters predate this PR) that CI doesn't catch because the Pester job runs under pwsh (PowerShell 7, UTF-8 by default). Replacing the three characters with ASCII equivalents (-, ..., ->) makes the installer parse and run on PS 5.1 while keeping pwsh behavior identical. Also removes the now-obsolete "integrity verification deferred" NOTE comment: the sibling PR #942 implements that verification and removes the same block, so deleting it here too keeps the two PRs mergeable in either order with no conflict. Same transliteration is applied verbatim in #942. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- install.ps1 | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/install.ps1 b/install.ps1 index 9f0cc0972..640300df2 100644 --- a/install.ps1 +++ b/install.ps1 @@ -3,7 +3,7 @@ # # Mirrors ./install (the bash installer for macOS/Linux): it downloads the # Bun-compiled standalone executable (altimate.exe) from GitHub releases and -# drops it in %USERPROFILE%\.altimate\bin — it does NOT depend on npm/Node. +# drops it in %USERPROFILE%\.altimate\bin - it does NOT depend on npm/Node. # # Usage: # powershell -c "irm https://www.altimate.sh/install.ps1 | iex" @@ -64,8 +64,8 @@ if ($Help) { exit 0 } -# A single P/Invoke type carries both native calls we need — the AVX2 CPU probe -# (kernel32) and the PATH-change broadcast (user32) — so we Add-Type once instead +# A single P/Invoke type carries both native calls we need - the AVX2 CPU probe +# (kernel32) and the PATH-change broadcast (user32) - so we Add-Type once instead # of compiling a throwaway type per call site. function Initialize-Native { if (-not ("Win32.AltimateNative" -as [type])) { @@ -101,21 +101,21 @@ function Test-Avx2 { Initialize-Native return [bool][Win32.AltimateNative]::IsProcessorFeaturePresent(40) } catch { - # If detection fails, assume no AVX2 and fall back to the baseline build — + # If detection fails, assume no AVX2 and fall back to the baseline build - # the baseline binary runs everywhere, an AVX2 binary on a non-AVX2 CPU crashes. return $false } } # --------------------------------------------------------------------------- -# Resolve version (once) — latest tag or a pinned release +# Resolve version (once) - latest tag or a pinned release # --------------------------------------------------------------------------- if ([string]::IsNullOrWhiteSpace($Version)) { $useLatest = $true # 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 + # 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++) { @@ -130,7 +130,7 @@ if ([string]::IsNullOrWhiteSpace($Version)) { if ($attempt -lt 3) { Start-Sleep -Seconds $attempt } } if ([string]::IsNullOrWhiteSpace($specificVersion)) { - Write-Muted "Could not resolve the latest version from GitHub (API unavailable) — installing the latest release anyway." + Write-Muted "Could not resolve the latest version from GitHub (API unavailable) - installing the latest release anyway." # Reset to $null (not ""): the already-installed short-circuit below compares # $installedVersion -eq $specificVersion. If the version probe of a missing or # corrupt binary also yields "", an "" -eq "" match would falsely report @@ -200,12 +200,6 @@ function Install-Target { $zipPath = Join-Path $tmpDir $filename try { - # NOTE: integrity verification (SHA256/signature) of the archive is - # intentionally deferred to match the bash installer's posture — both rely - # on HTTPS from github.com release assets. Releases do not currently publish - # a checksums file; adding one + verifying it in both installers is tracked - # as a follow-up. See PR #930 discussion. - # # Prefer curl.exe (ships with Windows 10 1803+) for a fast download with # --fail so HTTP errors don't write an error page to disk; fall back to # Invoke-WebRequest where curl.exe is unavailable. @@ -226,7 +220,7 @@ function Install-Target { # Windows locks a running .exe, so `altimate upgrade` (which re-runs this # installer) can't overwrite the binary that is currently executing. Windows - # *does* allow renaming a running exe — move the old one aside first, then + # *does* allow renaming a running exe - move the old one aside first, then # drop the new one in. Best-effort cleanup of the stale copy afterward. if (Test-Path $InstalledBinary) { $stale = "$InstalledBinary.old" @@ -253,7 +247,7 @@ if (-not $needsBaseline) { & $InstalledBinary --version *> $null $code = $LASTEXITCODE if ($code -eq 3221225501 -or $code -eq 1073741795 -or $code -eq -1073741795) { - Write-Muted "CPU lacks AVX2 — reinstalling the baseline build" + Write-Muted "CPU lacks AVX2 - reinstalling the baseline build" Install-Target -Baseline:$true } } From e10e32293440d0cf32d296bb7798823271a97741 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 17:34:31 +0800 Subject: [PATCH 4/4] test: update #930 release-validation for resilient version fetch The #952 release-validation suite asserted the latest path hard-fails with "Failed to fetch version information" (>=2) and that exit 1 appears >=3 times. This PR makes the latest path retry then degrade gracefully instead of aborting, so update those assertions: the latest path no longer hard-fails (the unsupported -arch and pinned-404 paths still exit 1, hence >=2). (This test never ran on this PR until it was retargeted from the merged feat/windows-powershell-installer branch to main.) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- .../windows-installer-930-codex.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts b/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts index 92e609129..879383881 100644 --- a/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts +++ b/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts @@ -51,13 +51,17 @@ describe("PR #930 install.ps1 release URL construction", () => { expect(versionBlock).toContain("exit 1") }) - test("latest-version resolution requires a nonblank GitHub release tag", () => { + test("latest-version resolution retries then degrades instead of hard-failing", () => { const versionBlock = scriptBlock("# Resolve version (once)", "# Skip if the requested version") expect(versionBlock).toContain("[string]::IsNullOrWhiteSpace($Version)") expect(versionBlock).toContain('"User-Agent" = "altimate-install"') expect(versionBlock).toContain("$specificVersion = ($rel.tag_name -replace '^v', '')") expect(versionBlock).toContain("[string]::IsNullOrWhiteSpace($specificVersion)") - expect(versionBlock.match(/Failed to fetch version information/g)?.length).toBeGreaterThanOrEqual(2) + // A transient releases/latest API blip must not abort the install: retry a few + // times, then degrade gracefully (the download resolves "latest" server-side). + expect(versionBlock).toContain("for ($attempt = 1; $attempt -le 3; $attempt++)") + expect(versionBlock).toContain("installing the latest release anyway") + expect(versionBlock).not.toContain("Failed to fetch version information") }) }) @@ -102,7 +106,10 @@ describe("PR #930 install.ps1 error handling and idempotency", () => { expect(INSTALL_PS1).toContain('$ErrorActionPreference = "Stop"') expect(INSTALL_PS1.match(/\btry\s*\{/g)?.length).toBeGreaterThanOrEqual(4) expect(INSTALL_PS1.match(/\bcatch\s*\{/g)?.length).toBeGreaterThanOrEqual(4) - expect(INSTALL_PS1.match(/exit 1/g)?.length).toBeGreaterThanOrEqual(3) + // The unsupported-arch and pinned-version-not-found paths still hard-fail + // (exit 1). The latest-version path no longer does: it degrades gracefully on + // a transient API blip rather than aborting the install. + expect(INSTALL_PS1.match(/exit 1/g)?.length).toBeGreaterThanOrEqual(2) }) test("skips reinstall when altimate or altimate-code already reports the target version", () => {