Skip to content

Improve Windows source-dev support and make scripts cross-platform#53664

Open
Heber-Hu wants to merge 1 commit intoopenclaw:mainfrom
Heber-Hu:main
Open

Improve Windows source-dev support and make scripts cross-platform#53664
Heber-Hu wants to merge 1 commit intoopenclaw:mainfrom
Heber-Hu:main

Conversation

@Heber-Hu
Copy link
Copy Markdown

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem:
  • Why it matters:
  • What changed:
  • What did NOT change (scope boundary):

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause:
  • Missing detection / guardrail:
  • Prior context (git blame, prior PR, issue, or refactor if known):
  • Why this regressed now:
  • If unknown, what was ruled out:

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should have caught this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
  • Scenario the test should lock in:
  • Why this is the smallest reliable guardrail:
  • Existing test that already covers this (if any):
  • If no new test is added, why not:

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

Security Impact (required)

  • New permissions/capabilities? (Yes/No)
  • Secrets/tokens handling changed? (Yes/No)
  • New/changed network calls? (Yes/No)
  • Command/tool execution surface changed? (Yes/No)
  • Data access scope changed? (Yes/No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Summary

This PR improves local development on Windows for OpenClaw source checkouts and makes several common repository scripts more cross-platform.

What changed

  • fix Windows source-build compatibility for runtime staging and A2UI bundling
  • add Windows-safe fallbacks when file symlinks are not permitted
  • resolve npm CLI lookup correctly on Windows Node installations
  • replace several shell-specific package scripts with Node helpers
  • make common dev scripts work in PowerShell without Unix-style env var prefixes
  • add clearer guards for macOS-only and bash-dependent scripts

Script improvements

  • gateway:dev
  • gateway:dev:reset
  • prepare
  • format:docs
  • format:docs:check
  • format:diff
  • deadcode:report:ci:*
  • protocol:check

Validation

  • verified pnpm install
  • verified pnpm build
  • verified pnpm openclaw --version
  • verified common script entrypoints after the cross-platform updates
  • verified the branch pushes cleanly from Windows to a fork

@openclaw-barnacle openclaw-barnacle bot added scripts Repository scripts size: L labels Mar 24, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR replaces Unix-specific shell idioms in package.json scripts with thin Node.js helper scripts (run-with-env.mjs, run-bash-script.mjs, require-platform-run.mjs, etc.), making local development on Windows substantially more workable. It also adds a Windows-safe copyFile fallback for file-symlink creation (EPERM) and fixes the npm-CLI lookup path for Windows Node installations.

Key changes:

  • New helper scriptsrun-with-env.mjs, run-bash-script.mjs, require-platform-run.mjs, format-docs.mjs, format-diff.mjs, deadcode-report-ci.mjs, protocol-check.mjs, prepare-git-hooks.mjs — are well-structured and correctly wire stdio/exit-codes.
  • prepare-git-hooks.mjs changes the prepare lifecycle script's error handling: the original one-liner guaranteed exit 0 via || exit 0, but the new script can exit non-zero if git config core.hooksPath git-hooks fails (e.g. git safe.directory restriction in CI, read-only .git/config). This could silently break pnpm install for affected environments.
  • deadcode-report-ci.mjs always returns 0 (matching the original || true), but an ENOENT from spawnSync itself escapes the top-level catch and exits 1, a minor inconsistency.
  • test:install:e2e:anthropic/test:install:e2e:openai in package.json delegate to run-with-env.mjs with bash as the command, bypassing the friendly "install Git Bash or WSL" error that the bash-specific scripts receive through run-bash-script.mjs.

Confidence Score: 4/5

  • Safe to merge after addressing the prepare-git-hooks.mjs exit-code regression; remaining feedback is non-blocking.
  • The PR achieves its stated goal cleanly and the new helpers are well-implemented. The one concrete concern is prepare-git-hooks.mjs which removes the original || exit 0 guarantee — in a CI environment with git safe.directory restrictions or a read-only config, pnpm install could start failing. The fix is a one-liner. All other issues are style-level.
  • scripts/prepare-git-hooks.mjs — the exit-code change from the original shell script needs to be validated or reverted to always return 0.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: scripts/prepare-git-hooks.mjs
Line: 25-31

Comment:
**`prepare` no longer silently succeeds on `git config` failure**

The original `prepare` script used `|| exit 0` at the end, which guaranteed the command always exited 0 regardless of whether `git config core.hooksPath git-hooks` succeeded or failed. The new implementation can exit non-zero when `configureHooks.status` is non-zero (e.g. if the repo is marked unsafe by git's `safe.directory` check, the `.git/config` is read-only in CI, or any other git configuration error).

A failed `prepare` causes `pnpm install` / `npm install` to abort, which would be a regression for affected environments.

Consider returning `0` unconditionally after the `git config` call to preserve the original defensive behaviour:

```suggestion
  const configureHooks = runGit(["config", "core.hooksPath", "git-hooks"], {
    stdio: "inherit",
  });
  // Mirrors the original `|| exit 0` — always succeed so pnpm install is never blocked.
  return 0;
}
```

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/deadcode-report-ci.mjs
Line: 32-42

Comment:
**CI artifact is written even when the underlying tool crashes**

`main()` always returns `0`, which is the intended parity with the original `|| true`. However, there's a subtle difference in what gets written: when `result.status !== 0` (e.g. the tool hard-crashed and produced no output), `result.stdout` and `result.stderr` may both be empty strings, so `sections.filter(Boolean)` will be empty and the file will contain just a bare `"\n"`. The old behaviour would have written an empty file.

More importantly, if `runPackageScript` itself throws before the `result` is assigned (e.g. `spawnSync` hits `ENOENT`), the top-level `catch` in the entry-point block will exit with code `1`, which *would* fail CI — an inconsistency compared to the `|| true` semantics. If you want full parity, consider wrapping the `spawnSync` call:

```suggestion
  let result;
  try {
    result = runPackageScript(scriptName);
  } catch {
    result = { stdout: "", stderr: "" };
  }
```

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/run-with-env.mjs
Line: 44-49

Comment:
**`bash` commands via `run-with-env` skip the friendly "install Git Bash" error**

`test:install:e2e:anthropic` and `test:install:e2e:openai` both delegate to `run-with-env.mjs` and then spawn `bash` directly. Unlike `run-bash-script.mjs`, this path does not probe for bash first, so on Windows without Git Bash / WSL the error will be a generic Node.js `ENOENT` rather than the actionable message.

Since `run-with-env.mjs` is a generic env-injection helper it probably shouldn't hard-code bash awareness, but the calling scripts in `package.json` could be changed to pipe through `run-bash-script.mjs` instead (passing the env separately), or `run-with-env.mjs` could accept a pre-flight hook. At a minimum, documenting the discrepancy would help future contributors.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Improve Windows source-dev support and m..." | Re-trigger Greptile

Comment on lines +25 to +31
const configureHooks = runGit(["config", "core.hooksPath", "git-hooks"], {
stdio: "inherit",
});
if (!configureHooks) {
return 0;
}
return configureHooks.status ?? 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 prepare no longer silently succeeds on git config failure

The original prepare script used || exit 0 at the end, which guaranteed the command always exited 0 regardless of whether git config core.hooksPath git-hooks succeeded or failed. The new implementation can exit non-zero when configureHooks.status is non-zero (e.g. if the repo is marked unsafe by git's safe.directory check, the .git/config is read-only in CI, or any other git configuration error).

A failed prepare causes pnpm install / npm install to abort, which would be a regression for affected environments.

Consider returning 0 unconditionally after the git config call to preserve the original defensive behaviour:

Suggested change
const configureHooks = runGit(["config", "core.hooksPath", "git-hooks"], {
stdio: "inherit",
});
if (!configureHooks) {
return 0;
}
return configureHooks.status ?? 0;
const configureHooks = runGit(["config", "core.hooksPath", "git-hooks"], {
stdio: "inherit",
});
// Mirrors the original `|| exit 0` — always succeed so pnpm install is never blocked.
return 0;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/prepare-git-hooks.mjs
Line: 25-31

Comment:
**`prepare` no longer silently succeeds on `git config` failure**

The original `prepare` script used `|| exit 0` at the end, which guaranteed the command always exited 0 regardless of whether `git config core.hooksPath git-hooks` succeeded or failed. The new implementation can exit non-zero when `configureHooks.status` is non-zero (e.g. if the repo is marked unsafe by git's `safe.directory` check, the `.git/config` is read-only in CI, or any other git configuration error).

A failed `prepare` causes `pnpm install` / `npm install` to abort, which would be a regression for affected environments.

Consider returning `0` unconditionally after the `git config` call to preserve the original defensive behaviour:

```suggestion
  const configureHooks = runGit(["config", "core.hooksPath", "git-hooks"], {
    stdio: "inherit",
  });
  // Mirrors the original `|| exit 0` — always succeed so pnpm install is never blocked.
  return 0;
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +32 to +42
const result = runPackageScript(scriptName);
const sections = [];
if (result.stdout) {
sections.push(result.stdout.trimEnd());
}
if (result.stderr) {
sections.push(result.stderr.trimEnd());
}
fs.writeFileSync(absoluteOutputPath, `${sections.filter(Boolean).join("\n")}\n`, "utf8");

return 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 CI artifact is written even when the underlying tool crashes

main() always returns 0, which is the intended parity with the original || true. However, there's a subtle difference in what gets written: when result.status !== 0 (e.g. the tool hard-crashed and produced no output), result.stdout and result.stderr may both be empty strings, so sections.filter(Boolean) will be empty and the file will contain just a bare "\n". The old behaviour would have written an empty file.

More importantly, if runPackageScript itself throws before the result is assigned (e.g. spawnSync hits ENOENT), the top-level catch in the entry-point block will exit with code 1, which would fail CI — an inconsistency compared to the || true semantics. If you want full parity, consider wrapping the spawnSync call:

Suggested change
const result = runPackageScript(scriptName);
const sections = [];
if (result.stdout) {
sections.push(result.stdout.trimEnd());
}
if (result.stderr) {
sections.push(result.stderr.trimEnd());
}
fs.writeFileSync(absoluteOutputPath, `${sections.filter(Boolean).join("\n")}\n`, "utf8");
return 0;
let result;
try {
result = runPackageScript(scriptName);
} catch {
result = { stdout: "", stderr: "" };
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/deadcode-report-ci.mjs
Line: 32-42

Comment:
**CI artifact is written even when the underlying tool crashes**

`main()` always returns `0`, which is the intended parity with the original `|| true`. However, there's a subtle difference in what gets written: when `result.status !== 0` (e.g. the tool hard-crashed and produced no output), `result.stdout` and `result.stderr` may both be empty strings, so `sections.filter(Boolean)` will be empty and the file will contain just a bare `"\n"`. The old behaviour would have written an empty file.

More importantly, if `runPackageScript` itself throws before the `result` is assigned (e.g. `spawnSync` hits `ENOENT`), the top-level `catch` in the entry-point block will exit with code `1`, which *would* fail CI — an inconsistency compared to the `|| true` semantics. If you want full parity, consider wrapping the `spawnSync` call:

```suggestion
  let result;
  try {
    result = runPackageScript(scriptName);
  } catch {
    result = { stdout: "", stderr: "" };
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +44 to +49
const child = spawn(command, args, {
cwd: process.cwd(),
env,
stdio: "inherit",
...(shouldUseShellForCommand(command) ? { shell: true } : {}),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 bash commands via run-with-env skip the friendly "install Git Bash" error

test:install:e2e:anthropic and test:install:e2e:openai both delegate to run-with-env.mjs and then spawn bash directly. Unlike run-bash-script.mjs, this path does not probe for bash first, so on Windows without Git Bash / WSL the error will be a generic Node.js ENOENT rather than the actionable message.

Since run-with-env.mjs is a generic env-injection helper it probably shouldn't hard-code bash awareness, but the calling scripts in package.json could be changed to pipe through run-bash-script.mjs instead (passing the env separately), or run-with-env.mjs could accept a pre-flight hook. At a minimum, documenting the discrepancy would help future contributors.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/run-with-env.mjs
Line: 44-49

Comment:
**`bash` commands via `run-with-env` skip the friendly "install Git Bash" error**

`test:install:e2e:anthropic` and `test:install:e2e:openai` both delegate to `run-with-env.mjs` and then spawn `bash` directly. Unlike `run-bash-script.mjs`, this path does not probe for bash first, so on Windows without Git Bash / WSL the error will be a generic Node.js `ENOENT` rather than the actionable message.

Since `run-with-env.mjs` is a generic env-injection helper it probably shouldn't hard-code bash awareness, but the calling scripts in `package.json` could be changed to pipe through `run-bash-script.mjs` instead (passing the env separately), or `run-with-env.mjs` could accept a pre-flight hook. At a minimum, documenting the discrepancy would help future contributors.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 535a23dc06

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (!configureHooks) {
return 0;
}
return configureHooks.status ?? 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep prepare hook setup best-effort

prepare is now hard-failing when git config core.hooksPath git-hooks returns non-zero, which regresses the previous behavior that always exited 0 (... && git config ... || exit 0). In environments where .git/config is temporarily unwriteable (for example a stale .git/config.lock or restricted checkout), pnpm install now fails even though hook setup should be non-blocking; this should remain best-effort to avoid breaking installs.

Useful? React with 👍 / 👎.

@Heber-Hu
Copy link
Copy Markdown
Author

Tested on a Windows source checkout and verified install/build plus the updated script entrypoints. Happy to make follow-up changes if maintainers want this split further or adjusted for repo conventions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scripts Repository scripts size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant