From ff5667a582293e11d7f5a78b3db99aa7809259eb Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 3 Jun 2026 15:39:31 +0200 Subject: [PATCH] fix(installer): fail on onboarding exit code --- CHANGELOG.md | 1 + scripts/install.ps1 | 5 +++- test/scripts/install-ps1.test.ts | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c59f2c557a7..bba162b1211f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai - Release/CI/E2E: fail the kitchen-sink RPC walk when command RSS sampling captures no process samples. - Release/CI/E2E: force-stop memory/fd repro gateway children that survive listener cleanup. - Release/CI/E2E: remove fallback ClawHub skill-install home directories when proof runs fail. +- Installers: fail the PowerShell installer when interactive onboarding exits non-zero. - Scripts/UI: stop descendant processes from wrapped non-interactive commands when `run-with-env` receives shutdown signals. - Release/CI/E2E: write multi-node update Docker artifacts to unique per-run directories by default so parallel runs cannot overwrite evidence. - Release/CI/E2E: write package Telegram Docker artifacts to unique per-run directories by default so parallel live/RTT runs cannot overwrite evidence. diff --git a/scripts/install.ps1 b/scripts/install.ps1 index f8484fe7d944..6fec0c6c9459 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -655,7 +655,10 @@ function Invoke-InteractiveOpenClawCommand { throw "openclaw command not found on PATH." } - $null = Start-Process -FilePath $commandPath -ArgumentList $Arguments -NoNewWindow -Wait -PassThru + $process = Start-Process -FilePath $commandPath -ArgumentList $Arguments -NoNewWindow -Wait -PassThru + if ($process.ExitCode -ne 0) { + throw "openclaw $($Arguments -join ' ') failed with exit code $($process.ExitCode)." + } } function Resolve-CommandPath { diff --git a/test/scripts/install-ps1.test.ts b/test/scripts/install-ps1.test.ts index cb3cdf1471c8..c67f81c89742 100644 --- a/test/scripts/install-ps1.test.ts +++ b/test/scripts/install-ps1.test.ts @@ -353,10 +353,58 @@ describe("install.ps1 failure handling", () => { expect(interactiveCommandBody).toContain("-NoNewWindow"); expect(interactiveCommandBody).toContain("-Wait"); expect(interactiveCommandBody).toContain("-PassThru"); + expect(interactiveCommandBody).toContain("$process.ExitCode -ne 0"); + expect(interactiveCommandBody).toContain("failed with exit code"); expect(mainBody).toContain('Write-Host "Starting setup..." -ForegroundColor Cyan'); expect(mainBody).toContain("Invoke-InteractiveOpenClawCommand onboard"); }); + runIfPowerShell("fails install when interactive onboarding exits non-zero", () => { + const tempDir = harness.createTempDir("openclaw-install-ps1-"); + const scriptPath = join(tempDir, "install.ps1"); + const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, ""); + writeFileSync( + scriptPath, + [ + scriptWithoutEntryPoint, + "", + "function Write-Banner { }", + "function Ensure-ExecutionPolicy { return $true }", + "function Check-Node { return $true }", + "function Check-ExistingOpenClaw { return $false }", + "function Get-NpmCommandPath { return 'npm.cmd' }", + "function Install-OpenClaw { return $true }", + "function Ensure-OpenClawOnPath { return $true }", + "function Add-ToUserPath { param([string]$Path) }", + "function Get-OpenClawCommandPath { return 'cmd.exe' }", + "function Start-Process {", + " param([string]$FilePath, [string[]]$ArgumentList, [switch]$NoNewWindow, [switch]$Wait, [switch]$PassThru)", + " [pscustomobject]@{ ExitCode = 17 }", + "}", + "$InstallMethod = 'npm'", + "$NoOnboard = $false", + "", + ...ENTRYPOINT_LINES, + "", + ].join("\n"), + ); + chmodSync(scriptPath, 0o755); + + const result = runPowerShell([ + "-NoLogo", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + scriptPath, + ]); + + expect(result.status).toBe(1); + expect(`${result.stdout}\n${result.stderr}`).toContain( + "openclaw onboard failed with exit code 17", + ); + }); + runIfPowerShell("exits non-zero when run as a script file", () => { const tempDir = harness.createTempDir("openclaw-install-ps1-"); const scriptPath = join(tempDir, "install.ps1");