fix(build): use Git Bash wrapper for A2UI bundling on Windows#44211
fix(build): use Git Bash wrapper for A2UI bundling on Windows#44211aniruddhaadak80 wants to merge 2 commits intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the reliability of the canvas:a2ui:bundle build step on native Windows by routing the existing bundle-a2ui.sh script through a Node launcher that attempts to locate Git Bash, avoiding Windows environments where bash resolves to the WSL stub or is missing.
Changes:
- Added a Node wrapper (
scripts/bundle-a2ui.mjs) that resolves Git Bash paths on Windows and invokesbundle-a2ui.sh. - Updated
package.jsonto run the new Node wrapper forpnpm canvas:a2ui:bundle.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/bundle-a2ui.mjs | New Windows-aware launcher to find Git Bash and execute the existing bundling script. |
| package.json | Switches canvas:a2ui:bundle to use the Node launcher instead of directly calling bash. |
scripts/bundle-a2ui.mjs
Outdated
| ? resolveWindowsBash() ?? "bash" | ||
| : "bash"; | ||
|
|
There was a problem hiding this comment.
On Windows, falling back to plain "bash" when resolveWindowsBash() returns null can still hit the System32 WSL stub (the original failure mode), so this wrapper may not actually fix the Windows build in that case. Consider either (a) treating a null resolution as a hard error with a clear message, or (b) searching PATH for bash.exe while explicitly excluding %SystemRoot%\System32\bash.exe / WSL stubs.
| ? resolveWindowsBash() ?? "bash" | |
| : "bash"; | |
| ? resolveWindowsBash() | |
| : "bash"; | |
| if (process.platform === "win32" && !shell) { | |
| console.error( | |
| "A2UI bundling requires Git Bash on Windows. Unable to locate bash.exe. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | |
| ); | |
| process.exit(1); | |
| } |
| if (candidate && fs.existsSync(candidate)) { | ||
| return candidate; |
There was a problem hiding this comment.
resolveWindowsBash() only checks existsSync(candidate). If OPENCLAW_GIT_BASH/GIT_BASH_PATH is set to a directory (or non-executable), existsSync will return true and spawnSync will then fail in a less obvious way. Prefer validating with statSync(candidate).isFile() (and optionally checking .exe suffix on win32) before returning it.
| if (candidate && fs.existsSync(candidate)) { | |
| return candidate; | |
| if (!candidate) { | |
| continue; | |
| } | |
| try { | |
| const stat = fs.statSync(candidate); | |
| if (!stat.isFile()) { | |
| continue; | |
| } | |
| if (process.platform === "win32") { | |
| const ext = path.extname(candidate).toLowerCase(); | |
| if (ext !== ".exe") { | |
| continue; | |
| } | |
| } | |
| return candidate; | |
| } catch { | |
| // Ignore invalid candidates and try the next one. |
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | ||
| ); | ||
| } | ||
| throw result.error; |
There was a problem hiding this comment.
Throwing result.error here will surface a Node stack trace in npm/pnpm output, which is noisy for a script entrypoint and inconsistent with other repo scripts that print a concise error then exit non-zero (e.g., scripts/test-force.ts). Consider printing result.error.message (plus the Git Bash hint) and calling process.exit(1) instead of throwing.
| throw result.error; | |
| console.error(result.error.message); | |
| process.exit(1); |
Greptile SummaryThis PR replaces the direct The wrapper is well-structured: it checks user-configurable environment variables ( Minor issues found:
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: scripts/bundle-a2ui.mjs
Line: 51
Comment:
**Missing newline at end of file**
The file is missing a trailing newline, as noted by the `\ No newline at end of file` marker in the diff. Most linters and POSIX tools expect files to end with a newline.
```suggestion
process.exit(result.status ?? 1);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: scripts/bundle-a2ui.mjs
Line: 42-48
Comment:
**Non-Windows error path lacks diagnostic context**
When `result.error` is set (e.g. `ENOENT`) and the platform is *not* Windows, the error is re-thrown with no accompanying message. On Linux/macOS this would be a misconfigured environment where `bash` is genuinely absent, which is unusual but possible in minimal CI containers. Adding a cross-platform fallback message makes the failure easier to diagnose without changing the throw semantics.
```suggestion
if (result.error) {
if (process.platform === "win32") {
console.error(
"A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.",
);
} else {
console.error(
`A2UI bundling failed to spawn '${shell}': ${result.error.message}`,
);
}
throw result.error;
}
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 49cf2b4 |
scripts/bundle-a2ui.mjs
Outdated
| throw result.error; | ||
| } | ||
|
|
||
| process.exit(result.status ?? 1); No newline at end of file |
There was a problem hiding this comment.
Missing newline at end of file
The file is missing a trailing newline, as noted by the \ No newline at end of file marker in the diff. Most linters and POSIX tools expect files to end with a newline.
| process.exit(result.status ?? 1); | |
| process.exit(result.status ?? 1); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/bundle-a2ui.mjs
Line: 51
Comment:
**Missing newline at end of file**
The file is missing a trailing newline, as noted by the `\ No newline at end of file` marker in the diff. Most linters and POSIX tools expect files to end with a newline.
```suggestion
process.exit(result.status ?? 1);
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| if (result.error) { | ||
| if (process.platform === "win32") { | ||
| console.error( | ||
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | ||
| ); | ||
| } | ||
| throw result.error; |
There was a problem hiding this comment.
Non-Windows error path lacks diagnostic context
When result.error is set (e.g. ENOENT) and the platform is not Windows, the error is re-thrown with no accompanying message. On Linux/macOS this would be a misconfigured environment where bash is genuinely absent, which is unusual but possible in minimal CI containers. Adding a cross-platform fallback message makes the failure easier to diagnose without changing the throw semantics.
| if (result.error) { | |
| if (process.platform === "win32") { | |
| console.error( | |
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | |
| ); | |
| } | |
| throw result.error; | |
| if (result.error) { | |
| if (process.platform === "win32") { | |
| console.error( | |
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | |
| ); | |
| } else { | |
| console.error( | |
| `A2UI bundling failed to spawn '${shell}': ${result.error.message}`, | |
| ); | |
| } | |
| throw result.error; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/bundle-a2ui.mjs
Line: 42-48
Comment:
**Non-Windows error path lacks diagnostic context**
When `result.error` is set (e.g. `ENOENT`) and the platform is *not* Windows, the error is re-thrown with no accompanying message. On Linux/macOS this would be a misconfigured environment where `bash` is genuinely absent, which is unusual but possible in minimal CI containers. Adding a cross-platform fallback message makes the failure easier to diagnose without changing the throw semantics.
```suggestion
if (result.error) {
if (process.platform === "win32") {
console.error(
"A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.",
);
} else {
console.error(
`A2UI bundling failed to spawn '${shell}': ${result.error.message}`,
);
}
throw result.error;
}
```
How can I resolve this? If you propose a fix, please make it concise.
This comment was marked as spam.
This comment was marked as spam.
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); | ||
| const scriptPath = path.join(rootDir, "scripts", "bundle-a2ui.sh"); |
There was a problem hiding this comment.
scriptPath is built with path.join, which will use backslashes on Windows. Git Bash/MSYS tools are generally happier with forward slashes (and the previous package.json invocation used scripts/bundle-a2ui.sh with /). Consider normalizing scriptPath to use / (e.g., replace \\ with /) before passing it to bash to avoid path-translation edge cases.
| const scriptPath = path.join(rootDir, "scripts", "bundle-a2ui.sh"); | |
| const scriptPath = path.join(rootDir, "scripts", "bundle-a2ui.sh").replace(/\\/g, "/"); |
| process.env["ProgramFiles(x86)"] && | ||
| path.join(process.env["ProgramFiles(x86)"], "Git", "bin", "bash.exe"), | ||
| process.env["ProgramFiles(x86)"] && | ||
| path.join(process.env["ProgramFiles(x86)"], "Git", "usr", "bin", "bash.exe"), |
There was a problem hiding this comment.
The Windows Git Bash lookup misses the common per-user installation location (%LOCALAPPDATA%\\Programs\\Git\\bin\\bash.exe / ...\\usr\\bin\\bash.exe). Without this, many “standard” Git for Windows installs done as “Just for me” won’t be detected and you’ll hit the fallback bash resolution again.
| path.join(process.env["ProgramFiles(x86)"], "Git", "usr", "bin", "bash.exe"), | |
| path.join(process.env["ProgramFiles(x86)"], "Git", "usr", "bin", "bash.exe"), | |
| process.env.LOCALAPPDATA && | |
| path.join(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe"), | |
| process.env.LOCALAPPDATA && | |
| path.join(process.env.LOCALAPPDATA, "Programs", "Git", "usr", "bin", "bash.exe"), |
| if (result.error) { | ||
| if (process.platform === "win32") { | ||
| console.error( | ||
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", |
There was a problem hiding this comment.
The error text only mentions OPENCLAW_GIT_BASH, but the resolver also accepts GIT_BASH_PATH. To reduce confusion when users try to follow the guidance, consider mentioning both supported env vars (or standardizing on one) in this message.
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH to bash.exe if Git is installed in a non-standard location.", | |
| "A2UI bundling requires Git Bash on Windows. Set OPENCLAW_GIT_BASH or GIT_BASH_PATH to the path to bash.exe if Git is installed in a non-standard location.", |
| const shell = process.platform === "win32" ? (resolveWindowsBash() ?? "bash") : "bash"; | ||
|
|
||
| const result = spawnSync(shell, [scriptPath], { | ||
| cwd: rootDir, | ||
| stdio: "inherit", | ||
| env: process.env, | ||
| }); |
There was a problem hiding this comment.
On Windows, if resolveWindowsBash() returns null you fall back to invoking bash anyway. If that resolves to the System32 WSL stub, the process will typically exit non-zero without setting result.error, so this wrapper won’t emit the helpful Git Bash guidance and the original failure mode remains. Consider failing fast when no Git Bash candidate is found, or (at least) printing the guidance when result.status !== 0 and the wrapper used the fallback bash.
Summary
pnpm canvas:a2ui:bundledepended onbashresolving correctly on Windows, which often hits the System32 WSL stub or no executable at all.build/build:strict-smoke.bundle-a2ui.shthrough a realbashexecutable.bundle-a2ui.shis unchanged.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
bashresolving to the WSL stub or onsh.exebeing present on PATH.Security Impact (required)
Repro + Verification
Environment
Steps
pnpm canvas:a2ui:bundleon native Windows.Expected
Actual
pnpm canvas:a2ui:bundleandpnpm build:strict-smokeboth complete successfully with the wrapper in place.Evidence
Human Verification (required)
pnpm canvas:a2ui:bundleandpnpm build:strict-smokeon Windows.Compatibility / Migration
OPENCLAW_GIT_BASHif Git Bash is installed in a custom location)Failure Recovery (if this breaks)
package.jsonandscripts/bundle-a2ui.mjs.