From c3b8e5c812356681c0ca64ce82700ca0d8b06b77 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 05:48:30 +0100 Subject: [PATCH] fix(release): stabilize windows npm install --- CHANGELOG.md | 1 + docs/install/installer.md | 2 +- package.json | 2 + pnpm-lock.yaml | 31 +++++++++++---- scripts/install.ps1 | 32 +++++++++++----- test/scripts/install-ps1.test.ts | 42 ++++++++++++++++++++- test/scripts/root-package-overrides.test.ts | 8 ++++ 7 files changed, 99 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a513b7ec3..5c69d6d78aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Docs: https://docs.openclaw.ai - Thinking/providers: resolve bundled provider thinking profiles through lightweight provider policy artifacts when startup-lazy providers are not active, so OpenAI Codex GPT-5.x keeps xhigh available in Gateway session validation. Fixes #74796. Thanks @maxschachere. - Security/Windows: ignore workspace `.env` system-path variables and resolve stale-process `taskkill.exe` from the validated Windows install root, preventing repository-local env files from redirecting cleanup helpers. Thanks @pgondhi987. - CLI/plugins: refresh persisted plugin registry policy in place for `plugins enable` and `plugins disable`, so routine toggles no longer rebuild and hash every plugin source when the target is already indexed. Thanks @vincentkoc. +- Windows/install: run npm from a writable installer temp directory and pin the Bedrock runtime dependency below a Windows ARM Node 24 npm resolver failure, so global OpenClaw installs no longer fail before onboarding. Thanks @mariozechner. - CLI/plugins: scope install and enable slot selection to the selected plugin manifest/runtime fallback, so plugin installs no longer load every plugin runtime or broad status snapshot just to update memory/context slots. Thanks @vincentkoc. - Plugins/TTS: keep bundled speech-provider discovery available on cold package Gateway paths and add bundled plugin matrix runtime probes for health, readiness, RPC, TTS discovery, and post-ready runtime-deps watchdog coverage. Refs #75283. Thanks @vincentkoc. - Google Meet/Twilio: show delegated voice call ID, DTMF, and intro-greeting state in `googlemeet doctor`, and avoid claiming DTMF was sent when no Meet PIN sequence was configured. Refs #72478. Thanks @DougButdorf. diff --git a/docs/install/installer.md b/docs/install/installer.md index e1750809549..54eeb1b877c 100644 --- a/docs/install/installer.md +++ b/docs/install/installer.md @@ -287,7 +287,7 @@ by default, plus git-checkout installs under the same prefix flow. If missing, attempts install via winget, then Chocolatey, then Scoop. Node 22 LTS, currently `22.14+`, remains supported for compatibility. - - `npm` method (default): global npm install using selected `-Tag` + - `npm` method (default): global npm install using selected `-Tag`, launched from a writable installer temp directory so shells opened in protected folders such as `C:\` still work - `git` method: clone/update repo, install/build with pnpm, and install wrapper at `%USERPROFILE%\.local\bin\openclaw.cmd` diff --git a/package.json b/package.json index c3d2a303364..c08cea4455f 100644 --- a/package.json +++ b/package.json @@ -1674,6 +1674,7 @@ "vitest": "^4.1.5" }, "overrides": { + "@aws-sdk/client-bedrock-runtime": "3.1024.0", "axios": "1.15.0", "follow-redirects": "1.16.0", "node-domexception": "npm:@nolyfill/domexception@1.0.28", @@ -1688,6 +1689,7 @@ "@anthropic-ai/sdk": "0.92.0", "hono": "4.12.14", "@hono/node-server": "1.19.14", + "@aws-sdk/client-bedrock-runtime": "3.1024.0", "axios": "1.15.0", "follow-redirects": "1.16.0", "defu": "6.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b425b7082f1..dcb3013412d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,7 @@ overrides: '@anthropic-ai/sdk': 0.92.0 hono: 4.12.14 '@hono/node-server': 1.19.14 + '@aws-sdk/client-bedrock-runtime': 3.1024.0 axios: 1.15.0 follow-redirects: 1.16.0 defu: 6.1.5 @@ -243,8 +244,8 @@ importers: specifier: 3.1041.0 version: 3.1041.0 '@aws-sdk/client-bedrock-runtime': - specifier: 3.1041.0 - version: 3.1041.0 + specifier: 3.1024.0 + version: 3.1024.0 '@aws-sdk/credential-provider-node': specifier: 3.972.39 version: 3.972.39 @@ -1699,8 +1700,8 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-bedrock-runtime@3.1041.0': - resolution: {integrity: sha512-1QehYO3jhdvNQ5mOKtwIiNV04y4aywaNZw9HzCp7SSYCX4yy+AGXc2hhYjCiMDUvQPIELuvbR8MXw81NGAj8ZQ==} + '@aws-sdk/client-bedrock-runtime@3.1024.0': + resolution: {integrity: sha512-nIhsn0/eYrL2fTh4kMO7Hpfmhv+AkkXl0KGNpD6+fdmotGvRBWcDv9/PmP/+sT6gvrKTYyzH3vu4efpTPzzP0Q==} engines: {node: '>=20.0.0'} '@aws-sdk/client-bedrock@3.1041.0': @@ -1831,6 +1832,10 @@ packages: resolution: {integrity: sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1024.0': + resolution: {integrity: sha512-eoyTMgd6OzoE1dq50um5Y53NrosEkWsjH0W6pswi7vrv1W9hY/7hR43jDcPevqqj+OQksf/5lc++FTqRlb8Y1Q==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1041.0': resolution: {integrity: sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==} engines: {node: '>=20.0.0'} @@ -7896,7 +7901,7 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1041.0': + '@aws-sdk/client-bedrock-runtime@3.1024.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 @@ -7910,7 +7915,7 @@ snapshots: '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/middleware-websocket': 3.972.16 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/token-providers': 3.1041.0 + '@aws-sdk/token-providers': 3.1024.0 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 @@ -8454,6 +8459,18 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@aws-sdk/token-providers@3.1024.0': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/token-providers@3.1041.0': dependencies: '@aws-sdk/core': 3.974.8 @@ -9529,7 +9546,7 @@ snapshots: '@mariozechner/pi-ai@0.71.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.1))(ws@8.20.0)(zod@4.4.1)': dependencies: '@anthropic-ai/sdk': 0.92.0(zod@4.4.1) - '@aws-sdk/client-bedrock-runtime': 3.1041.0 + '@aws-sdk/client-bedrock-runtime': 3.1024.0 '@google/genai': 1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.1)) '@mistralai/mistralai': 2.2.1 chalk: 5.6.2 diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 9b56c0a0cda..da6cf73edd9 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -220,7 +220,8 @@ function Invoke-NativeCommandCapture { param( [Parameter(Mandatory = $true)] [string]$FilePath, - [string[]]$Arguments = @() + [string[]]$Arguments = @(), + [string]$WorkingDirectory = "" ) $stdoutPath = [System.IO.Path]::GetTempFileName() @@ -253,12 +254,19 @@ function Invoke-NativeCommandCapture { ) } - $process = Start-Process -FilePath $startFilePath ` - -ArgumentList $startArguments ` - -Wait ` - -PassThru ` - -RedirectStandardOutput $stdoutPath ` - -RedirectStandardError $stderrPath + $startProcessArgs = @{ + FilePath = $startFilePath + ArgumentList = $startArguments + Wait = $true + PassThru = $true + RedirectStandardOutput = $stdoutPath + RedirectStandardError = $stderrPath + } + if (![string]::IsNullOrWhiteSpace($WorkingDirectory)) { + $startProcessArgs.WorkingDirectory = $WorkingDirectory + } + + $process = Start-Process @startProcessArgs return @{ ExitCode = $process.ExitCode @@ -270,6 +278,12 @@ function Invoke-NativeCommandCapture { } } +function Get-NpmWorkingDirectory { + $workingDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "openclaw-installer" + New-Item -ItemType Directory -Path $workingDirectory -Force | Out-Null + return $workingDirectory +} + function Install-OpenClawNpm { param([string]$Target = "latest") @@ -286,7 +300,7 @@ function Install-OpenClawNpm { $installSpec, "--no-fund", "--no-audit" - ) + ) -WorkingDirectory (Get-NpmWorkingDirectory) if ($installResult.Stdout) { Microsoft.PowerShell.Utility\Write-Host $installResult.Stdout } @@ -468,7 +482,7 @@ function Main { "config", "get", "prefix" - ) + ) -WorkingDirectory (Get-NpmWorkingDirectory) $npmPrefix = $prefixResult.Stdout if ($prefixResult.ExitCode -eq 0 -and $npmPrefix) { Add-ToPath -Path "$npmPrefix" diff --git a/test/scripts/install-ps1.test.ts b/test/scripts/install-ps1.test.ts index 56bcc2c2734..95a5810f800 100644 --- a/test/scripts/install-ps1.test.ts +++ b/test/scripts/install-ps1.test.ts @@ -73,6 +73,44 @@ describe("install.ps1 failure handling", () => { expect(completeInstallBody).toMatch(/\bthrow "OpenClaw installation failed with exit code/); }); + it("runs npm capture commands from a writable installer temp directory", () => { + const nativeCaptureBody = extractFunctionBody(source, "Invoke-NativeCommandCapture"); + const npmInstallBody = extractFunctionBody(source, "Install-OpenClawNpm"); + const mainBody = extractFunctionBody(source, "Main"); + expect(source).toContain("function Get-NpmWorkingDirectory {"); + expect(nativeCaptureBody).toContain('[string]$WorkingDirectory = ""'); + expect(nativeCaptureBody).toContain("$startProcessArgs.WorkingDirectory = $WorkingDirectory"); + expect(npmInstallBody).toContain("-WorkingDirectory (Get-NpmWorkingDirectory)"); + expect(mainBody).toContain("-WorkingDirectory (Get-NpmWorkingDirectory)"); + }); + + runIfPowerShell("creates a temp npm working directory", () => { + const tempDir = harness.createTempDir("openclaw-install-ps1-"); + const scriptPath = join(tempDir, "install.ps1"); + const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, ""); + writeFileSync( + scriptPath, + [ + scriptWithoutEntryPoint, + "", + "$result = Get-NpmWorkingDirectory", + 'if (!(Test-Path -LiteralPath $result)) { throw "missing $result" }', + 'if ($result -notmatch "openclaw-installer") { throw "unexpected $result" }', + "", + ].join("\n"), + ); + chmodSync(scriptPath, 0o755); + + const result = spawnSync( + powershell!, + ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath], + { encoding: "utf8" }, + ); + + expect(result.status).toBe(0); + expect(result.stderr).toBe(""); + }); + runIfPowerShell("exits non-zero when run as a script file", () => { const tempDir = harness.createTempDir("openclaw-install-ps1-"); const scriptPath = join(tempDir, "install.ps1"); @@ -125,7 +163,7 @@ describe("install.ps1 failure handling", () => { "function Ensure-Node { return $true }", "function Add-ToPath { param([string]$Path) }", "function Invoke-NativeCommandCapture {", - " param([string]$FilePath, [string[]]$Arguments)", + " param([string]$FilePath, [string[]]$Arguments, [string]$WorkingDirectory = '')", " return @{ ExitCode = 0; Stdout = 'npm stdout'; Stderr = 'npm stderr' }", "}", "$NoOnboard = $true", @@ -167,7 +205,7 @@ describe("install.ps1 failure handling", () => { " return $true", "}", "function Invoke-NativeCommandCapture {", - " param([string]$FilePath, [string[]]$Arguments)", + " param([string]$FilePath, [string[]]$Arguments, [string]$WorkingDirectory = '')", " return @{ ExitCode = 0; Stdout = 'npm prefix'; Stderr = '' }", "}", "$NoOnboard = $true", diff --git a/test/scripts/root-package-overrides.test.ts b/test/scripts/root-package-overrides.test.ts index 86c0961ad2f..ddbded1d7f9 100644 --- a/test/scripts/root-package-overrides.test.ts +++ b/test/scripts/root-package-overrides.test.ts @@ -15,6 +15,14 @@ function readRootManifest(): RootPackageManifest { } describe("root package override guardrails", () => { + it("pins the Bedrock runtime below the Windows ARM Node 24 npm resolver failure", () => { + const manifest = readRootManifest(); + const pnpmOverride = manifest.pnpm?.overrides?.["@aws-sdk/client-bedrock-runtime"]; + + expect(pnpmOverride).toBe("3.1024.0"); + expect(manifest.overrides?.["@aws-sdk/client-bedrock-runtime"]).toBe(pnpmOverride); + }); + it("pins the node-domexception alias exactly in npm and pnpm overrides", () => { const manifest = readRootManifest(); const pnpmOverride = manifest.pnpm?.overrides?.["node-domexception"];