Fix Windows install on PowerShell 5.1 (irm | iex and missing checksum sidecar) - #828
Conversation
… sidecar The documented Windows install path (irm <url> | iex) failed before running any install logic, and even with that fixed it then hard-failed when a release had no .sha256 checksum sidecar. - Remove [ValidateSet] from the $Flavor param. Under Invoke-Expression a [ValidateSet][string] param is initialised to "" and immediately validated, which is not in the set, so iex threw ValidateSetFailure before the script ran. Flavor values are still validated in Choose-Flavor against Get-SupportedFlavors, so behaviour is unchanged. The MESH_LLM_INSTALL_FLAVOR env default is now resolved in the body instead of the param block. - Treat a response-less WebException as a missing checksum sidecar. On Windows PowerShell 5.1, Invoke-WebRequest follows the GitHub release redirect and, on a 404 target, surfaces a response-less WebException rather than a clean 404, so the intended warn-and-continue path was unreachable and the install died with 'could not download checksum sidecar'. A required checksum (MESH_LLM_REQUIRE_CHECKSUM=1) is still enforced by the caller. Verified end-to-end on a Windows 11 box (PowerShell 5.1) over Tailscale: irm | iex now downloads, warns about the missing sidecar, extracts, updates PATH, and reports mesh-llm --version.
📝 WalkthroughWalkthrough
ChangesInstaller parameter and error handling updates
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@install.ps1`:
- Around line 309-318: Narrow the 5.1 workaround in Test-MissingChecksumResponse
so it only treats the specific response-less WebException that matches the
redirect→missing-asset symptom as “missing sidecar”: when
$ErrorRecord.Exception.Response is null AND $ErrorRecord.Exception -is
[System.Net.WebException], check the WebException.Status and message (e.g.
Status -eq [System.Net.WebExceptionStatus]::ConnectionClosed and the
Exception.Message contains "connection was closed unexpectedly" or similar
redirect/closed text) and return true only in that case; for any other
response-less WebException, do not treat it as a missing checksum (allow the
caller Assert-DownloadedFileChecksum to throw/handle normally). Reference
Test-MissingChecksumResponse, Assert-DownloadedFileChecksum, and
$ErrorRecord.Exception (System.Net.WebException).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| $response = $ErrorRecord.Exception.Response | ||
| if (-not $response) { | ||
| return $false | ||
| # Windows PowerShell 5.1 follows the GitHub release redirect and then, | ||
| # on a 404 target, surfaces the failure as a response-less WebException | ||
| # ("The request was aborted: The connection was closed unexpectedly.") | ||
| # rather than a clean 404 HttpWebResponse. Treat a response-less | ||
| # WebException as a missing sidecar so the warn-and-continue path | ||
| # remains reachable on 5.1. A genuinely required checksum is still | ||
| # enforced by the caller via $RequireSidecar. | ||
| return $ErrorRecord.Exception -is [System.Net.WebException] |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Windows PowerShell 5.1, when Invoke-WebRequest follows a GitHub release redirect to a missing asset, what System.Net.WebException status/message shape is produced, and how does that differ from transient transport failures such as DNS lookup errors, TLS failures, proxy failures, or connection resets?
💡 Result:
When Invoke-WebRequest in Windows PowerShell 5.1 follows a GitHub release redirect and the final redirected URL resolves to a missing asset, the failure is treated as an HTTP error response (not a transport failure). The produced exception has: 1) Exception type / PowerShell error shape - CategoryInfo: InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException - FullyQualifiedErrorId: WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand [1][2] - The terminating error message shown is of the form: “Invoke-WebRequest: The remote server returned an error: (404) Not Found.” [1][2] 2) System.Net.WebException status / message shape - It is a WebException with Status = ProtocolError for HTTP protocol-level errors. [3][4] - The “response” is available on the exception (e.g., you can read $_.Exception.Response.StatusCode / StatusDescription in catch). [1][5] How this differs from transient transport failures - DNS lookup / name resolution failures: these fail before an HTTP response is received and correspond to WebExceptionStatus.NameResolutionFailure (different Status than ProtocolError). [6][7] - TLS / certificate validation failures: these correspond to WebExceptionStatus.TrustFailure or related secure-channel failures (different Status than ProtocolError). [3][4] - Proxy failures: they do not produce an HTTP protocol response; they correspond to other WebExceptionStatus values rather than ProtocolError (check WebException.Status). [6][4] - Connection resets / premature connection close: correspond to WebExceptionStatus.ConnectionClosed / similar statuses, not ProtocolError. [3][4] In short: a missing redirected GitHub asset produces a WebException shaped like an HTTP 404 (ProtocolError) with an error message indicating the HTTP status and with an attached Response object, whereas DNS/TLS/proxy/reset issues produce WebExceptions whose Status indicates the transport/root-cause (NameResolutionFailure, TrustFailure, ConnectionClosed, etc.) and do not present an HTTP status code from the server response. [1][3][4][7]
Citations:
- 1: invoke-webrequest unhandled exception on 404 PowerShell/PowerShell#9009
- 2: https://stackoverflow.com/questions/58301706/how-to-differentiate-invoke-webrequest-exception-http-error-code-in-try-catch
- 3: https://learn.microsoft.com/en-us/dotnet/api/system.net.webexceptionstatus?view=net-10.0
- 4: https://learn.microsoft.com/en-us/dotnet/api/system.net.webexception?view=netframework-4.8.1
- 5: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.4
- 6: https://learn.microsoft.com/en-us/dotnet/api/system.net.webexception.status?view=net-10.0
- 7: https://stackoverflow.com/questions/25014110/system-net-webexception-the-remote-name-could-not-be-resolved
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the exact blocks around the referenced lines and the related checksum helpers.
file="install.ps1"
echo "== install.ps1: lines 280-360 =="
nl -ba "$file" | sed -n '280,360p'
echo
echo "== Find Assert-DownloadedFileChecksum and related helpers =="
rg -n "Assert-DownloadedFileChecksum|Test-MissingChecksumResponse|\\$RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM" "$file" || true
echo
echo "== Show Assert-DownloadedFileChecksum implementation =="
# Print a modest window around the function definition (avoid dumping huge files).
fn_line=$(rg -n "function\s+Assert-DownloadedFileChecksum" "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn_line:-}" ]]; then
start=$((fn_line-30)); end=$((fn_line+180))
nl -ba "$file" | sed -n "${start},${end}p"
else
echo "Function Assert-DownloadedFileChecksum not found in $file"
fi
echo
echo "== Show Test-MissingChecksumResponse implementation =="
fn_line=$(rg -n "function\s+Test-MissingChecksumResponse" "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn_line:-}" ]]; then
start=$((fn_line-30)); end=$((fn_line+180))
nl -ba "$file" | sed -n "${start},${end}p"
else
echo "Function Test-MissingChecksumResponse not found in $file"
fi
echo
echo "== Check for sidecar policy parity with install.sh and AGENTS.md =="
# Only search; don't assume paths. We'll try common filenames at repo root.
rg -n "require checksum|warn-and-continue|missing.*sidecar|MESH_LLM_REQUIRE_CHECKSUM" -S "AGENTS.md" "install.sh" 2>/dev/null || true
ls -la AGENTS.md install.sh 2>/dev/null || trueRepository: Mesh-LLM/mesh-llm
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="install.ps1"
echo "Repo files: "
ls -ლა . | head
echo
echo "== install.ps1 lines 280-360 (with line numbers via awk) =="
awk 'NR>=280 && NR<=360 {printf "%d\t%s\n", NR, $0}' "$file"
echo
echo "== Locate checksum helpers and related vars =="
rg -n "Assert-DownloadedFileChecksum|Test-MissingChecksumResponse|\$RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|warn-and-continue|sidecar" "$file" || true
echo
echo "== Show Assert-DownloadedFileChecksum implementation =="
start=$(rg -n "function[[:space:]]+Assert-DownloadedFileChecksum" "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${start:-}" ]]; then
end=$((start+220))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$file"
else
echo "Function Assert-DownloadedFileChecksum not found."
fi
echo
echo "== Show Test-MissingChecksumResponse implementation =="
start=$(rg -n "function[[:space:]]+Test-MissingChecksumResponse" "$file" | head -n1 | cut -d: -f1 || true)
if [[ -n "${start:-}" ]]; then
end=$((start+140))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$file"
else
echo "Function Test-MissingChecksumResponse not found."
fi
echo
echo "== Inspect AGENTS.md and install.sh for warn-and-continue / checksum policy =="
ls -la AGENTS.md install.sh 2>/dev/null || true
rg -n "warn-and-continue|missing.*sidecar|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|checksum" AGENTS.md install.sh 2>/dev/null || trueRepository: Mesh-LLM/mesh-llm
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find install.ps1 paths =="
fd -t f "install.ps1" . || true
echo
echo "== List repo root for likely policy files =="
ls -la .
echo
echo "== Show AGENTS.md / install.sh if present =="
ls -la AGENTS.md install.sh 2>/dev/null || true
echo
echo "== Locate referenced line block in install.ps1 =="
# If multiple install.ps1 exist, dump lines around 309-330 for each.
mapfile -t files < <(fd -t f "install.ps1" . || true)
if [[ ${`#files`[@]} -eq 0 ]]; then
echo "No install.ps1 found."
exit 0
fi
for f in "${files[@]}"; do
echo
echo "---- $f (lines 295-340) ----"
awk 'NR>=295 && NR<=340 {printf "%d\t%s\n", NR, $0}' "$f" | head -n 200
done
echo
echo "== Locate checksum helpers and missing-sidecar predicates in install.ps1 =="
for f in "${files[@]}"; do
echo
echo "---- $f symbols ----"
rg -n "Assert-DownloadedFileChecksum|Test-MissingChecksumResponse|\$RequireSidecar|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|missing.*sidecar|warn-and-continue" "$f" || true
done
echo
echo "== Show implementations for Assert-DownloadedFileChecksum + Test-MissingChecksumResponse (if found) =="
for f in "${files[@]}"; do
fn1=$(rg -n "function[[:space:]]+Assert-DownloadedFileChecksum" "$f" | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn1:-}" ]]; then
echo
echo "---- $f: Assert-DownloadedFileChecksum around line $fn1 ----"
start=$((fn1-20)); end=$((fn1+180))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$f"
fi
fn2=$(rg -n "function[[:space:]]+Test-MissingChecksumResponse" "$f" | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn2:-}" ]]; then
echo
echo "---- $f: Test-MissingChecksumResponse around line $fn2 ----"
start=$((fn2-20)); end=$((fn2+200))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$f"
fi
done
echo
echo "== Policy parity checks (AGENTS.md / install.sh) =="
for target in AGENTS.md install.sh; do
if [[ -f "$target" ]]; then
echo
echo "---- $target ----"
rg -n "warn-and-continue|missing.*sidecar|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|checksum" "$target" || true
fi
doneRepository: Mesh-LLM/mesh-llm
Length of output: 2975
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== install.ps1: lines 300-340 =="
awk 'NR>=300 && NR<=340 {printf "%d\t%s\n", NR, $0}' install.ps1
echo
echo "== install.ps1: locate missing-sidecar / checksum helpers =="
rg -n "Assert-DownloadedFileChecksum|Test-MissingChecksumResponse|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|missing.*sidecar|warn-and-continue|checksum" install.ps1
echo
echo "== install.ps1: show Assert-DownloadedFileChecksum function body (small window) =="
fn=$(rg -n "function[[:space:]]+Assert-DownloadedFileChecksum" install.ps1 | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn:-}" ]]; then
start=$((fn-10)); end=$((fn+220))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' install.ps1
else
echo "Not found"
fi
echo
echo "== install.ps1: show Test-MissingChecksumResponse function body (small window) =="
fn=$(rg -n "function[[:space:]]+Test-MissingChecksumResponse" install.ps1 | head -n1 | cut -d: -f1 || true)
if [[ -n "${fn:-}" ]]; then
start=$((fn-10)); end=$((fn+220))
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' install.ps1
else
echo "Not found"
fi
echo
echo "== AGENTS.md: find warn-and-continue / checksum policy =="
rg -n "warn-and-continue|missing.*sidecar|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|checksum|sidecar" AGENTS.md || true
echo
echo "== install.sh: find matching checksum/sidecar policy =="
rg -n "warn-and-continue|missing.*sidecar|RequireSidecar|MESH_LLM_REQUIRE_CHECKSUM|checksum|sidecar" install.sh || trueRepository: Mesh-LLM/mesh-llm
Length of output: 19252
Narrow “missing checksum sidecar” handling for response-less WebException
install.ps1’s Test-MissingChecksumResponse treats any response-less System.Net.WebException ($ErrorRecord.Exception.Response is null ⇒ -is [System.Net.WebException]) as “missing sidecar”, and Assert-DownloadedFileChecksum then warns and returns, skipping SHA-256 verification unless $RequireSidecar/$RequireChecksum is set. This can downgrade transient transport failures (DNS/TLS/proxy/connection resets) into an integrity-unverified install, which diverges from AGENTS.md/install.sh’s warn-and-continue policy for actually missing .sha256 assets (HTTP 404/410).
Update the 5.1 workaround to gate on the specific WebException shape/status/message that corresponds to the redirect→missing-asset behavior described in the comment, and throw for other response-less transport failures instead of accepting all WebExceptions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@install.ps1` around lines 309 - 318, Narrow the 5.1 workaround in
Test-MissingChecksumResponse so it only treats the specific response-less
WebException that matches the redirect→missing-asset symptom as “missing
sidecar”: when $ErrorRecord.Exception.Response is null AND
$ErrorRecord.Exception -is [System.Net.WebException], check the
WebException.Status and message (e.g. Status -eq
[System.Net.WebExceptionStatus]::ConnectionClosed and the Exception.Message
contains "connection was closed unexpectedly" or similar redirect/closed text)
and return true only in that case; for any other response-less WebException, do
not treat it as a missing checksum (allow the caller
Assert-DownloadedFileChecksum to throw/handle normally). Reference
Test-MissingChecksumResponse, Assert-DownloadedFileChecksum, and
$ErrorRecord.Exception (System.Net.WebException).
Windows users can now install mesh-llm with the documented one-liner, and the install no longer dies when a release has no checksum sidecar.
What you can now do
On a stock Windows box (Windows PowerShell 5.1, the default), the documented command now works end to end:
Previously this failed immediately with a
ValidateSetFailurebefore any install logic ran, and even when invoked as a downloaded.ps1it then hard-failed on releases that ship no.sha256sidecar.Bugs fixed
irm | iexthrew before running. The$Flavorparameter had[ValidateSet(...)]with an env-var default. UnderInvoke-Expression, a[ValidateSet][string]parameter is initialised to"", which is immediately validated against the set, is not a member, and throwsValidateSetFailure. The[ValidateSet]is removed from the param; flavor values are still validated inChoose-FlavoragainstGet-SupportedFlavors, andMESH_LLM_INSTALL_FLAVORis now resolved in the body. Behaviour for an explicit/invalid-Flavoris unchanged (still throwsunsupported Windows flavor).Hard failure on a missing checksum sidecar (PowerShell 5.1). When a release has no
<archive>.sha256,Invoke-WebRequeston Windows PowerShell 5.1 follows the GitHubreleases/latest/downloadredirect and, on the 404 target, surfaces a response-lessWebException(The request was aborted: The connection was closed unexpectedly.) rather than a clean 404.Test-MissingChecksumResponsereturned$falsefor that, so the intended warn-and-continue path was unreachable and the install died withcould not download checksum sidecar. A response-lessWebExceptionis now treated as a missing sidecar.MESH_LLM_REQUIRE_CHECKSUM=1still hard-fails on a missing sidecar via the caller.This keeps the existing installer policy: verify when the sidecar exists, warn and continue when it is missing, and fail only when a checksum is explicitly required.
Validation
Tested end to end on a Windows 11 desktop (Intel i5-12400F, Windows PowerShell 5.1) reached over Tailscale, against the current published release (which has no sidecars):
irm | iexfailed withValidateSetFailure; running the file failed withcould not download checksum sidecar.cudaflavor, downloads the bundle, warnsChecksum sidecar not found; continuing without archive verification, extracts, updates the userPath, and printsmesh-llm 0.71.0.Example output:
Note: this PR only fixes installation. A separate issue tracks a runtime crash (
STATUS_ILLEGAL_INSTRUCTION 0xC000001D) on CPUs without AVX-512 once inference runs; that is a build-flag fix in the native runtime, addressed separately.Summary by CodeRabbit
Bug Fixes
Chores