From 899acfe42ffd2632e0ba0ac6c3893ea0afebdc66 Mon Sep 17 00:00:00 2001 From: xxxigm Date: Wed, 10 Jun 2026 19:14:11 +0700 Subject: [PATCH 1/2] fix(install/windows): repair stale winget registration; refresh PATH after every package manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ripgrep/ffmpeg is missing, `winget install ` on a package winget already has registered is treated as an upgrade: it finds no newer version and exits 0x8A15002B (-1978335189, APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) without ensuring the binary is actually present. The installer only logged that code and judged success by `Get-Command rg`, so a stale registration (files removed outside winget, or a missing alias shim) became a permanent dead-end — winget kept reporting "already installed" and the user could never reinstall. Detect that exit code and retry once with `--force` to repair the registration so the shim reappears. Also refresh the process PATH after the choco and scoop fallbacks (not just winget) via a shared helper, so a successful fallback install — or any install on a box without winget — is no longer misreported as "not installed". --- scripts/install.ps1 | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 21e3d495816..23a602ca9e8 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -892,6 +892,22 @@ function Test-Node { return $true } +function Update-ProcessPathForPackages { + # Rebuild the current process PATH from the persisted User+Machine hives plus + # winget's alias-shim directory, so a freshly-installed shim (rg.exe, + # ffmpeg.exe) becomes visible to Get-Command in THIS process without + # spawning a new shell. Called after every package-manager attempt + # (winget/choco/scoop): previously PATH was only refreshed inside the winget + # branch, so a successful choco/scoop fallback -- or any install on a box + # without winget -- could be misreported as "not installed". + $envPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") + $wingetLinks = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Links" + if (Test-Path $wingetLinks) { + $envPath = "$envPath;$wingetLinks" + } + $env:Path = $envPath +} + function Install-SystemPackages { $script:HasRipgrep = $false $script:HasFfmpeg = $false @@ -961,25 +977,33 @@ function Install-SystemPackages { try { $output = winget install --exact --id $pkg --source winget --silent ` --accept-package-agreements --accept-source-agreements 2>&1 + $code = $LASTEXITCODE $output | Out-File -FilePath $log -Encoding utf8 - "winget exit: $LASTEXITCODE" | Out-File -FilePath $log -Encoding utf8 -Append + "winget exit: $code" | Out-File -FilePath $log -Encoding utf8 -Append + # 0x8A15002B (-1978335189) = APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE. + # winget treats `install` on a package it already has registered as + # an *upgrade*, finds no newer version, and bails with this code -- + # even when the binary is gone from disk/PATH (stale registration, + # files removed outside winget, or a missing alias shim). We KNOW the + # command was missing (that's why we're here), so a plain install + # dead-ends forever. Force a reinstall to repair the registration so + # the shim reappears. + if ($code -eq -1978335189) { + "-> already-installed/no-upgrade; retrying with --force" | Out-File -FilePath $log -Encoding utf8 -Append + $output = winget install --exact --id $pkg --source winget --silent --force ` + --accept-package-agreements --accept-source-agreements 2>&1 + $output | Out-File -FilePath $log -Encoding utf8 -Append + "winget exit (force): $LASTEXITCODE" | Out-File -FilePath $log -Encoding utf8 -Append + } } catch { $_ | Out-File -FilePath $log -Encoding utf8 -Append "winget exit: " | Out-File -FilePath $log -Encoding utf8 -Append } } - # Refresh PATH from both env-var hives AND winget's alias shim directory. - # winget exposes packages via "command line aliases" in %LOCALAPPDATA%\ - # Microsoft\WinGet\Links, which is added to PATH by the AppExecutionAlias - # machinery only in *newly-spawned* shells -- not the current process. - # Without this addition, Get-Command rg below would falsely return null - # immediately after a successful install. - $wingetLinks = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Links" - $envPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") - if (Test-Path $wingetLinks) { - $envPath = "$envPath;$wingetLinks" - } - $env:Path = $envPath + # Refresh PATH so packages winget exposed via "command line aliases" in + # %LOCALAPPDATA%\Microsoft\WinGet\Links (added to PATH only in + # newly-spawned shells, not this process) are visible to Get-Command below. + Update-ProcessPathForPackages if ($needRipgrep -and (Get-Command rg -ErrorAction SilentlyContinue)) { Write-Success "ripgrep installed" $script:HasRipgrep = $true @@ -1005,6 +1029,7 @@ function Install-SystemPackages { foreach ($pkg in $chocoPkgs) { try { choco install $pkg -y 2>&1 | Out-Null } catch { } } + Update-ProcessPathForPackages if ($needRipgrep -and (Get-Command rg -ErrorAction SilentlyContinue)) { Write-Success "ripgrep installed via chocolatey" $script:HasRipgrep = $true @@ -1023,6 +1048,7 @@ function Install-SystemPackages { foreach ($pkg in $scoopPkgs) { try { scoop install $pkg 2>&1 | Out-Null } catch { } } + Update-ProcessPathForPackages if ($needRipgrep -and (Get-Command rg -ErrorAction SilentlyContinue)) { Write-Success "ripgrep installed via scoop" $script:HasRipgrep = $true From 9662b76d592025c1536a6d7e0cb9aa317c09cc4e Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:49:58 +0530 Subject: [PATCH 2/2] fix(install/windows): merge PATH in Update-ProcessPathForPackages instead of overwriting Follow-up to the winget stale-registration fix. Update-ProcessPathForPackages rebuilt $env:Path wholesale from the persisted User+Machine hives (plus winget's Links dir), discarding any process-only PATH entries added earlier in the installer run. Since the helper now runs after every package manager, that wholesale replace is more likely to clobber a process-local entry than the original winget-branch-only version was. Merge instead: seed from the current process PATH, then append hive and winget-Links entries not already present, with a case-insensitive, order-preserving dedupe. Behaviour on a clean box is unchanged (the hive entries are simply appended); the difference is that pre-existing process-only entries now survive the refresh. --- scripts/install.ps1 | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 23a602ca9e8..b316a99e4f7 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -893,19 +893,39 @@ function Test-Node { } function Update-ProcessPathForPackages { - # Rebuild the current process PATH from the persisted User+Machine hives plus - # winget's alias-shim directory, so a freshly-installed shim (rg.exe, - # ffmpeg.exe) becomes visible to Get-Command in THIS process without - # spawning a new shell. Called after every package-manager attempt - # (winget/choco/scoop): previously PATH was only refreshed inside the winget - # branch, so a successful choco/scoop fallback -- or any install on a box - # without winget -- could be misreported as "not installed". - $envPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") + # Make freshly-installed shims (rg.exe, ffmpeg.exe) visible to Get-Command in + # THIS process without spawning a new shell, by folding the persisted + # User+Machine hives plus winget's alias-shim directory into $env:Path. + # Called after every package-manager attempt (winget/choco/scoop): previously + # PATH was only refreshed inside the winget branch, so a successful + # choco/scoop fallback -- or any install on a box without winget -- could be + # misreported as "not installed". + # + # MERGE rather than overwrite: start from the existing process PATH so any + # process-only entries added earlier in this installer run survive, then + # APPEND hive/winget-Links entries not already present (case-insensitive, + # order-preserving dedupe). A wholesale replace would silently drop those + # process-only entries. + $candidates = @() + $candidates += $env:Path + $candidates += [Environment]::GetEnvironmentVariable("Path", "User") + $candidates += [Environment]::GetEnvironmentVariable("Path", "Machine") $wingetLinks = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Links" if (Test-Path $wingetLinks) { - $envPath = "$envPath;$wingetLinks" + $candidates += $wingetLinks } - $env:Path = $envPath + $seen = New-Object System.Collections.Generic.HashSet[string] ([StringComparer]::OrdinalIgnoreCase) + $ordered = New-Object System.Collections.Generic.List[string] + foreach ($chunk in $candidates) { + if ([string]::IsNullOrEmpty($chunk)) { continue } + foreach ($entry in $chunk.Split(';')) { + $trimmed = $entry.Trim() + if ($trimmed -and $seen.Add($trimmed)) { + $ordered.Add($trimmed) + } + } + } + $env:Path = [string]::Join(';', $ordered) } function Install-SystemPackages {