Skip to content

Make env-prefixed npm scripts work on Windows#45380

Open
GMTekAI wants to merge 3 commits intoopenclaw:mainfrom
GMTekAI:codex/windows-env-scripts
Open

Make env-prefixed npm scripts work on Windows#45380
GMTekAI wants to merge 3 commits intoopenclaw:mainfrom
GMTekAI:codex/windows-env-scripts

Conversation

@GMTekAI
Copy link
Copy Markdown

@GMTekAI GMTekAI commented Mar 13, 2026

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: several npm scripts used Unix-style inline env assignment, which fails on Windows shells.
  • Why it matters: pnpm gateway:dev failed immediately on Windows before OpenClaw could even start.
  • What changed: added a small Node helper to apply env vars cross-platform and updated the affected scripts to use it.
  • What did NOT change (scope boundary): no runtime gateway logic, config semantics, or feature behavior changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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 #

User-visible / Behavior Changes

Windows users can now run env-prefixed npm scripts like pnpm gateway:dev without the shell failing on inline env assignment.

Security Impact (required)

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

Repro + Verification

Environment

  • OS: Windows
  • Runtime/container: Node v22.22.1, pnpm 10.23.0
  • Model/provider: N/A
  • Integration/channel (if any): N/A
  • Relevant config (redacted): none required

Steps

  1. Clone the repo on Windows.
  2. Run pnpm install.
  3. Run pnpm gateway:dev.

Expected

  • The dev gateway command starts normally.

Actual

  • Before this change, the command failed immediately with:
    'OPENCLAW_SKIP_CHANNELS' is not recognized as an internal or external command

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: reproduced the Windows shell failure before the change, then reran pnpm gateway:dev after the change and confirmed it no longer failed at script startup.
  • Edge cases checked: updated the other affected env-prefixed scripts to use the same helper so the fix is consistent across similar commands.
  • What you did not verify: I did not run the full test suite or validate every updated script on every platform.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR or restore the previous script entries in package.json.
  • Files/config to restore: package.json, scripts/with-env.mjs
  • Known bad symptoms reviewers should watch for: script invocation failures if the helper is not found or if env forwarding behaves differently than expected on non-Windows shells.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: the helper could slightly change how env-prefixed scripts are executed compared with native Unix shell assignment.
    • Mitigation: the change is limited to scripts already relying on inline env vars, and the helper only sets env vars then forwards the command.

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

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR fixes a Windows compatibility issue where npm scripts using inline Unix-style env assignment would fail immediately on Windows shells. The fix introduces a small Node.js helper (scripts/with-env.mjs) that parses key-value environment assignments before a -- separator, merges them into the child process environment, and spawns the command — working identically on Windows and Unix.

Key changes:

  • Added scripts/with-env.mjs: a cross-platform env-injection helper using child_process.spawn with shell: true only on Windows.
  • Updated 8 npm scripts in package.json to use the new helper (gateway:dev, gateway:dev:reset, tui:dev, test:live, test:macmini, test:install:e2e:anthropic, test:install:e2e:openai, android:test:integration).
  • No runtime gateway logic, feature behaviour, or config semantics were changed.

Minor concerns flagged:

  • The helper lacks an error event listener on the spawned child process — an unresolvable command produces an unhandled Node.js exception rather than a clean error message.
  • When the child exits due to a signal, the script exits with code 1 instead of the conventional 128 + signal_number, which can confuse CI systems that distinguish signal termination from normal failures.

Confidence Score: 4/5

  • Safe to merge — changes are limited to dev-time npm scripts and a new helper with no impact on runtime behavior.
  • The approach is correct and well-scoped: spawn with conditional shell: true on Windows is the right pattern, argument parsing is defensive (guards against missing = and empty keys), and all affected scripts were updated consistently. Two minor robustness gaps exist in the helper (missing error event handler and imprecise signal exit code), but neither will affect normal usage of the dev scripts.
  • scripts/with-env.mjs — the missing error handler and signal exit code are worth a quick follow-up.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: scripts/with-env.mjs
Line: 34-43

Comment:
**Missing `error` event handler and imprecise signal exit code**

Two small robustness gaps in the spawn setup:

1. There is no `error` listener. If `spawn` fails (e.g. command not found, permission denied), Node.js will throw an unhandled exception and print a raw stack trace instead of the clean usage message. Adding an `error` handler gives a much friendlier failure mode.

2. When the child is killed by a signal (e.g. `SIGINT` from Ctrl-C), the handler exits with a hard-coded `1`. The Unix convention is `128 + signal_number` (e.g. `130` for SIGINT). Some CI systems and shell scripts inspect this exit code to distinguish a normal failure from a signal termination; returning `1` can mask that.

Suggested fix:

```suggestion
const child = spawn(command, args, {
  cwd: process.cwd(),
  env: childEnv,
  stdio: "inherit",
  shell: process.platform === "win32",
});

const SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15, SIGKILL: 9 };

child.on("error", (err) => {
  console.error(`with-env: failed to start command "${command}": ${err.message}`);
  process.exit(1);
});

child.on("exit", (code, signal) => {
  if (signal) {
    process.exit(128 + (SIGNALS[signal] ?? 1));
  }
  process.exit(code ?? 1);
});
```

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

Last reviewed commit: 865fb63

Comment on lines +34 to +43
const child = spawn(command, args, {
cwd: process.cwd(),
env: childEnv,
stdio: "inherit",
shell: process.platform === "win32",
});

child.on("exit", (code, signal) => {
process.exit(signal ? 1 : (code ?? 1));
});
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.

Missing error event handler and imprecise signal exit code

Two small robustness gaps in the spawn setup:

  1. There is no error listener. If spawn fails (e.g. command not found, permission denied), Node.js will throw an unhandled exception and print a raw stack trace instead of the clean usage message. Adding an error handler gives a much friendlier failure mode.

  2. When the child is killed by a signal (e.g. SIGINT from Ctrl-C), the handler exits with a hard-coded 1. The Unix convention is 128 + signal_number (e.g. 130 for SIGINT). Some CI systems and shell scripts inspect this exit code to distinguish a normal failure from a signal termination; returning 1 can mask that.

Suggested fix:

Suggested change
const child = spawn(command, args, {
cwd: process.cwd(),
env: childEnv,
stdio: "inherit",
shell: process.platform === "win32",
});
child.on("exit", (code, signal) => {
process.exit(signal ? 1 : (code ?? 1));
});
const child = spawn(command, args, {
cwd: process.cwd(),
env: childEnv,
stdio: "inherit",
shell: process.platform === "win32",
});
const SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15, SIGKILL: 9 };
child.on("error", (err) => {
console.error(`with-env: failed to start command "${command}": ${err.message}`);
process.exit(1);
});
child.on("exit", (code, signal) => {
if (signal) {
process.exit(128 + (SIGNALS[signal] ?? 1));
}
process.exit(code ?? 1);
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/with-env.mjs
Line: 34-43

Comment:
**Missing `error` event handler and imprecise signal exit code**

Two small robustness gaps in the spawn setup:

1. There is no `error` listener. If `spawn` fails (e.g. command not found, permission denied), Node.js will throw an unhandled exception and print a raw stack trace instead of the clean usage message. Adding an `error` handler gives a much friendlier failure mode.

2. When the child is killed by a signal (e.g. `SIGINT` from Ctrl-C), the handler exits with a hard-coded `1`. The Unix convention is `128 + signal_number` (e.g. `130` for SIGINT). Some CI systems and shell scripts inspect this exit code to distinguish a normal failure from a signal termination; returning `1` can mask that.

Suggested fix:

```suggestion
const child = spawn(command, args, {
  cwd: process.cwd(),
  env: childEnv,
  stdio: "inherit",
  shell: process.platform === "win32",
});

const SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15, SIGKILL: 9 };

child.on("error", (err) => {
  console.error(`with-env: failed to start command "${command}": ${err.message}`);
  process.exit(1);
});

child.on("exit", (code, signal) => {
  if (signal) {
    process.exit(128 + (SIGNALS[signal] ?? 1));
  }
  process.exit(code ?? 1);
});
```

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5934aa1. I updated scripts/with-env.mjs to handle spawn errors cleanly and to return signal-style exit codes instead of collapsing signaled exits to code 1.

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: 865fb63980

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});

child.on("exit", (code, signal) => {
process.exit(signal ? 1 : (code ?? 1));
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 Propagate child termination signals

When the wrapped command exits due to a signal, this wrapper always returns exit code 1 instead of preserving the signal semantics (typically 128 + signal from the shell path). For scripts like gateway:dev/test:*, this changes behavior from the previous inline-env form and can misclassify interrupted runs (SIGINT/SIGTERM) as generic failures in tooling that distinguishes cancellation from test failures.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5934aa1. I updated scripts/with-env.mjs to handle spawn errors cleanly and to return signal-style exit codes instead of collapsing signaled exits to code 1.

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: 5934aa1af3

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


child.on("exit", (code, signal) => {
if (signal) {
process.exit(128 + (SIGNAL_EXIT_CODES[signal] ?? 1));
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 Preserve child signal exit codes for all signals

with-env only maps four signal names, so when a child exits from any other signal (for example SIGQUIT) this wrapper returns 129 via the ?? 1 fallback instead of 128 + signal_number (131 for SIGQUIT). That changes failure classification versus prior shell behavior and can misreport interrupted runs in tooling that distinguishes specific signal terminations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in ebdc245. I switched with-env to use Node's built-in os.constants.signals map so signal-style exit codes are preserved for all supported signals, not just a hand-picked subset.

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

Labels

scripts Repository scripts size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant