From b0eae97c03f476ee540ae325f5a2cbcc6d4ab17d Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 28 Jul 2026 19:42:21 +0200 Subject: [PATCH] fix(windows): keep install.ps1 pure ASCII so PowerShell can parse it install.ps1 did not run. It failed to PARSE, dying with a cascade of syntax errors before its first statement, and it has been in that state on main since 61a432c. Windows PowerShell 5.1 decodes a BOM-less .ps1 as ANSI, not UTF-8. The three em-dashes in this file are 3 UTF-8 bytes each, and cp1252 renders them as a sequence ENDING IN A DOUBLE QUOTE. Two sat in comments and were harmless; the third sat inside a string literal: "error: could not retire the existing $BinName - close all running" where the injected quote closed the string early and left the rest of the line as garbage expression, taking the enclosing if/foreach blocks with it. This shipped broken because nothing executed the script. The smoke covers it in Phase 13, but every Windows smoke run since the launcher removal aborted at an earlier phase, and the VirusTotal check only ever scanned the bytes. Since install.ps1 is now the sole Windows install AND update path, a parse error there is a total loss of both. Fixed as ASCII rather than by adding a BOM: the documented install pipes the file straight into the parser (irm ... | iex), where a BOM would become part of the string and break that path instead. The bundle contract now rejects any non-ASCII codepoint in install.ps1 and names it, so this cannot return silently. Signed-off-by: Martin Vogel --- install.ps1 | 6 +++--- tests/test_windows_bundle_contract.sh | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/install.ps1 b/install.ps1 index 60df83477..f455c983d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,4 +1,4 @@ -# install.ps1 — One-line installer for codebase-memory-mcp (Windows). +# install.ps1 - One-line installer for codebase-memory-mcp (Windows). # # Usage: see README.md for install instructions. # @@ -103,7 +103,7 @@ foreach ($arg in $args) { # PROCESSOR_ARCHITEW6432, which is unset for 64-bit emulated processes. Fall back # to the env vars only if the .NET API is somehow unavailable. if ($env:CBM_ARCH) { - # Explicit override wins — used by CI/tests, and an escape hatch under x64 + # Explicit override wins - used by CI/tests, and an escape hatch under x64 # emulation on ARM64 where no in-process detection is reliable. $Arch = $env:CBM_ARCH } else { @@ -279,7 +279,7 @@ if (Test-Path -LiteralPath $Dest -PathType Leaf) { catch { Start-Sleep -Milliseconds 500 } } if (-not $renamed) { - Write-Host "error: could not retire the existing $BinName — close all running" -ForegroundColor Red + Write-Host "error: could not retire the existing $BinName - close all running" -ForegroundColor Red Write-Host " codebase-memory-mcp sessions and coding agents, then re-run." -ForegroundColor Red Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue exit 1 diff --git a/tests/test_windows_bundle_contract.sh b/tests/test_windows_bundle_contract.sh index 780c13027..532462d1b 100644 --- a/tests/test_windows_bundle_contract.sh +++ b/tests/test_windows_bundle_contract.sh @@ -164,6 +164,19 @@ for archive, call, ui in ( # ".retired-", then installs over the freed name. Without the # retire step every Windows update fails on a locked destination. installer = read("install.ps1") +# Windows PowerShell 5.1 decodes a BOM-less .ps1 as ANSI, so a UTF-8 em-dash +# arrives as three cp1252 characters ending in a double quote. Inside a string +# literal that quote closes it early and the whole script dies with a cascade of +# parse errors before its first statement runs. A BOM would fix the file on disk +# but corrupt the documented `irm ... | iex` path, which pipes the bytes +# straight into the parser -- so the shipped installer stays pure ASCII. +non_ascii = sorted({ch for ch in installer if ord(ch) > 0x7F}) +require( + not non_ascii, + "install.ps1 must be pure ASCII (found: " + + ", ".join(f"U+{ord(ch):04X}" for ch in non_ascii) + + ")", +) require( "$Dest = Join-Path $InstallDir $BinName" in installer, "install.ps1 must resolve the canonical install destination as $Dest",