Skip to content

Commit 2edd6e2

Browse files
committed
fix(installer): fail failed Windows git builds
1 parent e0405ec commit 2edd6e2

3 files changed

Lines changed: 97 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai
4747

4848
### Fixes
4949

50+
- Windows installer: fail Git checkout installs when `pnpm install` or `pnpm build` fails instead of writing a wrapper to a missing CLI build.
5051
- Agents: keep parallel OpenAI-compatible tool-call deltas in separate argument buffers so interleaved tool calls no longer corrupt streamed arguments. (#82263) Thanks @luna-system.
5152
- Memory/doctor: report missing or unusable QMD workspace directories as workspace failures instead of generic binary failures. (#63167) Thanks @sercada.
5253
- Debug proxy: record CONNECT client-socket errors and destroy the paired upstream socket so abrupt client disconnects no longer leak tunnel resources. (#82444) Thanks @SebTardif.

scripts/install.ps1

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ function Fail-Install {
2323
return $false
2424
}
2525

26+
function Test-BooleanSuccessResult {
27+
param([object[]]$Results)
28+
29+
return ($Results.Count -gt 0 -and $Results[-1] -eq $true)
30+
}
31+
2632
function Complete-Install {
2733
param([bool]$Succeeded)
2834

@@ -1043,23 +1049,37 @@ function Install-OpenClawFromGit {
10431049
Push-Location -LiteralPath $RepoDir
10441050
$pushedRepoLocation = $true
10451051
& $pnpmCommand install
1052+
if ($LASTEXITCODE -ne 0) {
1053+
Write-Host "[!] pnpm install failed for the Git checkout" -ForegroundColor Red
1054+
return $false
1055+
}
10461056
if (-not (& $pnpmCommand ui:build)) {
10471057
Write-Host "[!] UI build failed; continuing (CLI may still work)" -ForegroundColor Yellow
10481058
}
10491059
& $pnpmCommand build
1060+
if ($LASTEXITCODE -ne 0) {
1061+
Write-Host "[!] pnpm build failed for the Git checkout" -ForegroundColor Red
1062+
return $false
1063+
}
10501064
} finally {
10511065
if ($pushedRepoLocation) {
10521066
Pop-Location
10531067
}
10541068
$env:NPM_CONFIG_SCRIPT_SHELL = $prevPnpmScriptShell
10551069
}
10561070

1071+
$entryPath = Join-Path $RepoDir "dist\\entry.js"
1072+
if (-not (Test-Path $entryPath)) {
1073+
Write-Host "[!] OpenClaw build did not produce $entryPath" -ForegroundColor Red
1074+
return $false
1075+
}
1076+
10571077
$binDir = Join-Path $env:USERPROFILE ".local\\bin"
10581078
if (-not (Test-Path $binDir)) {
10591079
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
10601080
}
10611081
$cmdPath = Join-Path $binDir "openclaw.cmd"
1062-
$cmdContents = "@echo off`r`nnode ""$RepoDir\\dist\\entry.js"" %*`r`n"
1082+
$cmdContents = "@echo off`r`nnode ""$entryPath"" %*`r`n"
10631083
Set-Content -Path $cmdPath -Value $cmdContents -NoNewline
10641084

10651085
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
@@ -1202,7 +1222,8 @@ function Main {
12021222
}
12031223
} catch { }
12041224
$finalGitDir = $GitDir
1205-
if (-not (Install-OpenClawFromGit -RepoDir $GitDir -SkipUpdate:$NoGitUpdate)) {
1225+
$gitInstallResults = @(Install-OpenClawFromGit -RepoDir $GitDir -SkipUpdate:$NoGitUpdate)
1226+
if (-not (Test-BooleanSuccessResult -Results $gitInstallResults)) {
12061227
return (Fail-Install)
12071228
}
12081229
} else {
@@ -1211,7 +1232,8 @@ function Main {
12111232
Remove-Item -Force $gitWrapper
12121233
Write-Host "[OK] Removed git wrapper (switching to npm)" -ForegroundColor Green
12131234
}
1214-
if (-not (Install-OpenClaw)) {
1235+
$npmInstallResults = @(Install-OpenClaw)
1236+
if (-not (Test-BooleanSuccessResult -Results $npmInstallResults)) {
12151237
return (Fail-Install)
12161238
}
12171239
}
@@ -1321,5 +1343,5 @@ function Main {
13211343
}
13221344

13231345
$mainResults = @(Main)
1324-
$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true
1346+
$installSucceeded = Test-BooleanSuccessResult -Results $mainResults
13251347
Complete-Install -Succeeded:$installSucceeded

test/scripts/install-ps1.test.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { createScriptTestHarness } from "./test-helpers";
66

77
const SCRIPT_PATH = "scripts/install.ps1";
88
const ENTRYPOINT_RE =
9-
/\r?\n\$mainResults = @\(Main\)\r?\n\$installSucceeded = \$mainResults\.Count -gt 0 -and \$mainResults\[-1\] -eq \$true\r?\nComplete-Install -Succeeded:\$installSucceeded\s*$/m;
9+
/\r?\n\$mainResults = @\(Main\)\r?\n\$installSucceeded = Test-BooleanSuccessResult -Results \$mainResults\r?\nComplete-Install -Succeeded:\$installSucceeded\s*$/m;
10+
const ENTRYPOINT_LINES = [
11+
"$mainResults = @(Main)",
12+
"$installSucceeded = Test-BooleanSuccessResult -Results $mainResults",
13+
"Complete-Install -Succeeded:$installSucceeded",
14+
];
1015

1116
function extractFunctionBody(source: string, name: string): string {
1217
const match = source.match(
@@ -50,9 +55,7 @@ function createFailingNodeFixture(source: string): string {
5055
"function Check-Node { return $false }",
5156
"function Install-Node { return $false }",
5257
"",
53-
"$mainResults = @(Main)",
54-
"$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true",
55-
"Complete-Install -Succeeded:$installSucceeded",
58+
...ENTRYPOINT_LINES,
5659
"",
5760
].join("\n");
5861
}
@@ -77,9 +80,12 @@ describe("install.ps1 failure handling", () => {
7780

7881
it("keeps failure termination in the top-level completion handler", () => {
7982
const completeInstallBody = extractFunctionBody(source, "Complete-Install");
83+
const booleanSuccessBody = extractFunctionBody(source, "Test-BooleanSuccessResult");
8084
expect(completeInstallBody).toMatch(/\$PSCommandPath/);
8185
expect(completeInstallBody).toMatch(/\bexit \$script:InstallExitCode\b/);
8286
expect(completeInstallBody).toMatch(/\bthrow "OpenClaw installation failed with exit code/);
87+
expect(booleanSuccessBody).toContain("$Results.Count -gt 0");
88+
expect(source).toContain("$installSucceeded = Test-BooleanSuccessResult -Results $mainResults");
8389
});
8490

8591
it("runs npm install through the resolved command with quiet CI defaults", () => {
@@ -227,6 +233,7 @@ describe("install.ps1 failure handling", () => {
227233
const pnpmVersionMatchBody = extractFunctionBody(source, "Test-PnpmCommandMatchesVersion");
228234
const ensurePnpmBody = extractFunctionBody(source, "Ensure-Pnpm");
229235
const gitInstallBody = extractFunctionBody(source, "Install-OpenClawFromGit");
236+
const mainBody = extractFunctionBody(source, "Main");
230237

231238
expect(pnpmVersionBody).toContain("package.json");
232239
expect(pnpmVersionBody).toContain("$packageJson.packageManager -match '^pnpm@(?<version>[^+]+)'");
@@ -247,9 +254,31 @@ describe("install.ps1 failure handling", () => {
247254
expect(gitInstallBody.indexOf("git -C $RepoDir pull --rebase")).toBeLessThan(
248255
gitInstallBody.indexOf("Ensure-Pnpm -RepoDir $RepoDir"),
249256
);
257+
expect(mainBody).toContain("$gitInstallResults = @(Install-OpenClawFromGit");
258+
expect(mainBody).toContain(
259+
"Test-BooleanSuccessResult -Results $gitInstallResults",
260+
);
261+
expect(mainBody).toContain("$npmInstallResults = @(Install-OpenClaw)");
262+
expect(mainBody).toContain(
263+
"Test-BooleanSuccessResult -Results $npmInstallResults",
264+
);
250265
expect(gitInstallBody).toContain("Push-Location -LiteralPath $RepoDir");
251266
expect(gitInstallBody).toContain("& $pnpmCommand install");
267+
expect(gitInstallBody).toContain(
268+
'Write-Host "[!] pnpm install failed for the Git checkout"',
269+
);
270+
expect(gitInstallBody).toContain("& $pnpmCommand build");
271+
expect(gitInstallBody).toContain(
272+
'Write-Host "[!] pnpm build failed for the Git checkout"',
273+
);
274+
expect(gitInstallBody).toContain('$entryPath = Join-Path $RepoDir "dist\\\\entry.js"');
275+
expect(gitInstallBody).toContain("Test-Path $entryPath");
276+
expect(gitInstallBody).toContain(
277+
'Write-Host "[!] OpenClaw build did not produce $entryPath"',
278+
);
279+
expect(gitInstallBody).toContain('node ""$entryPath"" %*');
252280
expect(gitInstallBody).not.toContain("& $pnpmCommand -C $RepoDir install");
281+
expect(gitInstallBody).not.toContain('node ""$RepoDir\\\\dist\\\\entry.js"" %*');
253282
});
254283

255284
it("cleans legacy git submodules only from the selected git checkout", () => {
@@ -309,6 +338,42 @@ describe("install.ps1 failure handling", () => {
309338
expect(result.stdout).toContain("alive-after-install");
310339
});
311340

341+
runIfPowerShell("treats noisy Git install false as failure", () => {
342+
const tempDir = harness.createTempDir("openclaw-install-ps1-");
343+
const scriptPath = join(tempDir, "install.ps1");
344+
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
345+
writeFileSync(
346+
scriptPath,
347+
[
348+
scriptWithoutEntryPoint,
349+
"",
350+
"function Write-Banner { }",
351+
"function Ensure-ExecutionPolicy { return $true }",
352+
"function Check-Node { return $true }",
353+
"function Check-ExistingOpenClaw { return $false }",
354+
"function Get-NpmCommandPath { return $null }",
355+
"function Install-OpenClawFromGit {",
356+
" Write-Output 'pnpm stdout before failure'",
357+
" return $false",
358+
"}",
359+
"function Ensure-OpenClawOnPath { throw 'should not continue after failed git install' }",
360+
"$InstallMethod = 'git'",
361+
"$GitDir = 'C:\\\\openclaw-test'",
362+
"$NoOnboard = $true",
363+
"$result = Main",
364+
'if ($result -ne $false) { throw "Main returned $result" }',
365+
'if ($script:InstallExitCode -ne 1) { throw "InstallExitCode=$script:InstallExitCode" }',
366+
"",
367+
].join("\n"),
368+
);
369+
chmodSync(scriptPath, 0o755);
370+
371+
const result = runPowerShell(["-NoLogo", "-NoProfile", "-Command", `. ${toPowerShellSingleQuotedLiteral(scriptPath)}`]);
372+
373+
expect(result.status).toBe(0);
374+
expect(result.stderr).toBe("");
375+
});
376+
312377
runIfPowerShell("keeps npm chatter out of Main's success return value", () => {
313378
const tempDir = harness.createTempDir("openclaw-install-ps1-");
314379
const scriptPath = join(tempDir, "install.ps1");
@@ -371,9 +436,7 @@ describe("install.ps1 failure handling", () => {
371436
"function Refresh-GatewayServiceIfLoaded { }",
372437
"function Invoke-OpenClawCommand { return 'OpenClaw test-version' }",
373438
"$NoOnboard = $true",
374-
"$mainResults = @(Main)",
375-
"$installSucceeded = $mainResults.Count -gt 0 -and $mainResults[-1] -eq $true",
376-
"Complete-Install -Succeeded:$installSucceeded",
439+
...ENTRYPOINT_LINES,
377440
"",
378441
].join("\n"),
379442
);

0 commit comments

Comments
 (0)