Skip to content

shell: support custom shells on Windows#65271

Closed
trudbot wants to merge 4 commits intoopenclaw:mainfrom
trudbot:codex/windows-shell-config-clean
Closed

shell: support custom shells on Windows#65271
trudbot wants to merge 4 commits intoopenclaw:mainfrom
trudbot:codex/windows-shell-config-clean

Conversation

@trudbot
Copy link
Copy Markdown

@trudbot trudbot commented Apr 12, 2026

Summary

  • add Windows custom shell support via OPENCLAW_SHELL and SHELL
  • keep OPENCLAW_SHELL as the highest-priority override on every platform
  • choose shell arguments by shell type (/c for cmd, PowerShell flags for pwsh / powershell, -c for Unix-like shells)
  • ignore invalid POSIX-style paths on Windows and fall back cleanly
  • accept quoted Windows shell paths such as "C:\Program Files\Git\bin\bash.exe"

Problem

OpenClaw previously hardcoded PowerShell on Windows and ignored SHELL, which made it awkward to use Git Bash or other Unix-like shells for command execution. That caused practical issues around quoting and escaping, especially for commands that behave better in a Unix-like shell.

There were also a few edge cases worth handling carefully:

  • OPENCLAW_SHELL should still win over SHELL
  • explicit pwsh / powershell overrides must use PowerShell flags, not -c
  • invalid Windows values like /usr/bin/bash should not break shell resolution
  • quoted Windows env var paths should still resolve to the intended executable

What Changed

This PR updates src/agents/shell-utils.ts so that:

  • on Windows, getShellConfig() first tries OPENCLAW_SHELL, then SHELL
  • each configured value is validated independently, so a stale override does not block a valid fallback
  • PowerShell, cmd, and Unix-like shells get the correct argument style automatically
  • detectRuntimeShell() stays consistent with the execution-side resolution logic
  • non-Windows platforms still honor OPENCLAW_SHELL override precedence as before

Examples

$env:SHELL = 'C:\Program Files\Git\bin\bash.exe'
$env:OPENCLAW_SHELL = '"C:\Program Files\Git\bin\bash.exe"'

Both forms now resolve correctly on Windows.

Testing

Local verification:

  • pnpm test src/agents/shell-utils.test.ts

Added coverage for:

  • Windows custom shell override precedence
  • fallback from invalid POSIX-style Windows paths
  • explicit pwsh / cmd handling
  • quoted Windows shell paths
  • non-Windows OPENCLAW_SHELL precedence in detectRuntimeShell()

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M labels Apr 12, 2026
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: bf12734f2c

ℹ️ 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".

Comment thread src/agents/shell-utils.ts
Comment on lines +155 to +159
!looksLikeExplicitPath(trimmed) &&
runtimeShell !== "cmd" &&
runtimeShell !== "powershell" &&
runtimeShell !== "pwsh" &&
!isUnixLikeShellName(runtimeShell)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate configured Windows shell path exists before using it

A stale OPENCLAW_SHELL/SHELL path like C:\\missing\\bash.exe is currently accepted as soon as it "looks like" a path, so getShellConfig() returns a non-existent executable and downstream spawn fails with ENOENT instead of falling back to the next candidate or PowerShell. This is a regression on Windows (previous behavior always had a working PowerShell fallback) and directly breaks command execution for users with outdated shell path env vars.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

This PR adds Windows custom shell support to src/agents/shell-utils.ts, letting users specify their preferred shell via OPENCLAW_SHELL or SHELL on Windows (previously hardcoded to PowerShell). The new resolveConfiguredWindowsShell helper correctly handles arg-style selection (/c for cmd, -NoProfile -NonInteractive -Command for PowerShell variants, -c for unix-like shells), strips paired quotes from env-var paths, and rejects invalid POSIX-style paths on Windows with a clean fallback. Both getShellConfig() and detectRuntimeShell() are updated to use the same resolution logic, fixing a pre-existing inconsistency between the two functions on Windows.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style suggestions that do not affect correctness in realistic configurations.

The implementation is well-structured with correct arg-style selection, proper POSIX path rejection, quote-stripping, and ordered OPENCLAW_SHELL → SHELL fallback. Tests cover all new branches. The one flagged issue (isUnixLikeShellName substring match) only matters if a user deliberately sets SHELL to a non-shell binary on Windows, which is very unlikely in practice.

No files require special attention; the shell resolution logic in shell-utils.ts is straightforward to follow and well-tested.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/shell-utils.ts
Line: 129-138

Comment:
**`name.includes("sh")` matches non-shell programs**

The substring check `name.includes("sh")` is overly broad: values like `"ssh"`, `"tsh"`, or `"tmsh"` will satisfy `isUnixLikeShellName`, causing them to be accepted as a unix-like shell on Windows. A user with `SHELL=ssh` (not implausible in developer tooling environments) would end up with `{ shell: "ssh", args: ["-c"] }` instead of falling through to the PowerShell default.

Consider using a closed allowlist of recognised short names instead:

```suggestion
function isUnixLikeShellName(name: string): boolean {
  return (
    name === "nu" ||
    name === "nushell" ||
    name === "fish" ||
    name === "bash" ||
    name === "zsh" ||
    name === "ksh" ||
    name === "sh" ||
    name === "ash" ||
    name === "dash" ||
    name === "csh" ||
    name === "tcsh" ||
    name === "mksh" ||
    name === "pdksh"
  );
}
```
Or keep the prefix/contains approach but explicitly exclude known non-shell programs that contain "sh".

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

Reviews (1): Last reviewed commit: "Merge branch 'main' into codex/windows-s..." | Re-trigger Greptile

Comment thread src/agents/shell-utils.ts
Comment on lines +129 to +138
function isUnixLikeShellName(name: string): boolean {
return (
name === "nu" ||
name === "nushell" ||
name === "fish" ||
name.includes("bash") ||
name.includes("zsh") ||
name.includes("ksh") ||
name.includes("sh")
);
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 name.includes("sh") matches non-shell programs

The substring check name.includes("sh") is overly broad: values like "ssh", "tsh", or "tmsh" will satisfy isUnixLikeShellName, causing them to be accepted as a unix-like shell on Windows. A user with SHELL=ssh (not implausible in developer tooling environments) would end up with { shell: "ssh", args: ["-c"] } instead of falling through to the PowerShell default.

Consider using a closed allowlist of recognised short names instead:

Suggested change
function isUnixLikeShellName(name: string): boolean {
return (
name === "nu" ||
name === "nushell" ||
name === "fish" ||
name.includes("bash") ||
name.includes("zsh") ||
name.includes("ksh") ||
name.includes("sh")
);
function isUnixLikeShellName(name: string): boolean {
return (
name === "nu" ||
name === "nushell" ||
name === "fish" ||
name === "bash" ||
name === "zsh" ||
name === "ksh" ||
name === "sh" ||
name === "ash" ||
name === "dash" ||
name === "csh" ||
name === "tcsh" ||
name === "mksh" ||
name === "pdksh"
);
}

Or keep the prefix/contains approach but explicitly exclude known non-shell programs that contain "sh".

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/shell-utils.ts
Line: 129-138

Comment:
**`name.includes("sh")` matches non-shell programs**

The substring check `name.includes("sh")` is overly broad: values like `"ssh"`, `"tsh"`, or `"tmsh"` will satisfy `isUnixLikeShellName`, causing them to be accepted as a unix-like shell on Windows. A user with `SHELL=ssh` (not implausible in developer tooling environments) would end up with `{ shell: "ssh", args: ["-c"] }` instead of falling through to the PowerShell default.

Consider using a closed allowlist of recognised short names instead:

```suggestion
function isUnixLikeShellName(name: string): boolean {
  return (
    name === "nu" ||
    name === "nushell" ||
    name === "fish" ||
    name === "bash" ||
    name === "zsh" ||
    name === "ksh" ||
    name === "sh" ||
    name === "ash" ||
    name === "dash" ||
    name === "csh" ||
    name === "tcsh" ||
    name === "mksh" ||
    name === "pdksh"
  );
}
```
Or keep the prefix/contains approach but explicitly exclude known non-shell programs that contain "sh".

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: 836d3078fc

ℹ️ 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".

Comment thread src/agents/shell-utils.ts
Comment on lines +130 to +132
if (!looksLikeExplicitPath(value)) {
return true;
}
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 Resolve bare Windows shell names before accepting override

resolveConfiguredWindowsShellValue currently accepts any non-path shell token because hasResolvableWindowsShellPath returns true when looksLikeExplicitPath is false. That means values like OPENCLAW_SHELL=bash (or SHELL=bash) are treated as valid even when bash is not installed or not on PATH, so getShellConfig() returns an executable that later fails with ENOENT instead of falling back to the next candidate or PowerShell. Please validate bare names against PATH/PATHEXT before returning them as a resolved shell.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex automated review: keeping this open.

Keep this PR open. Current main still hardcodes Windows host exec to PowerShell and does not expose a custom Windows shell selection surface in code, config, or docs. The PR is a live implementation candidate, but its proposed OPENCLAW_SHELL override conflicts with documented runtime-marker semantics and still has review concerns around validating configured shell names and paths. Related open PR #39413 tracks a broader config-backed solution, but it is not merged and should be consolidated through maintainer review rather than used as an auto-close reason.

Best possible solution:

Keep this PR open for maintainer review and consolidate direction with #39413. The best implementation should choose a stable public surface, likely config-backed tools.exec.shell / per-agent override or a deliberately named environment override, validate Windows explicit paths and bare names against PATH/PATHEXT, preserve PowerShell defaults, and update config schema, docs, and regression tests together.

What I checked:

  • Current Windows exec shell selection is still hardcoded: getShellConfig() returns resolvePowerShellPath() with -NoProfile -NonInteractive -Command whenever process.platform === "win32"; that branch does not consult SHELL, OPENCLAW_SHELL, or config. (src/agents/shell-utils.ts:54, 4878d3e059ce)
  • Host exec uses getShellConfig on the command execution path: The exec runtime builds childArgv from getShellConfig() before spawning host commands, so the hardcoded Windows selection affects actual user-facing exec behavior. (src/agents/bash-tools.exec-runtime.ts:708, 4878d3e059ce)
  • Docs still document PowerShell fallback, not custom Windows shells: The exec docs say non-Windows hosts use SHELL, while Windows hosts prefer PowerShell 7 discovery and then Windows PowerShell 5.1. The same section documents OPENCLAW_SHELL=exec as a spawned-command context marker. Public docs: docs/tools/exec.md. (docs/tools/exec.md:73, 4878d3e059ce)
  • OPENCLAW_SHELL is already reserved in public environment docs: Environment docs define OPENCLAW_SHELL values such as exec, acp, acp-client, and tui-local as runtime-injected markers, making reuse as a user shell override a product/API decision. Public docs: docs/help/environment.md. (docs/help/environment.md:59, 4878d3e059ce)
  • Current config schema has no exec shell override: ExecToolConfig and the runtime zod shape include host, security, ask, node, PATH, safe-bin, timeout, cleanup, notification, and apply-patch fields, but no shell field. (src/config/types.tools.ts:242, 4878d3e059ce)
  • Live PR context shows this remains an open implementation candidate: GitHub API shows this PR is open and unmerged, touching only src/agents/shell-utils.ts and src/agents/shell-utils.test.ts. Review comments flag unresolved validation concerns for missing configured shell paths and unresolved bare shell names.

Remaining risk / open question:

  • Closing now could discard a plausible Windows exec usability fix that is not implemented on current main.
  • The proposed OPENCLAW_SHELL override conflicts with current runtime-marker docs and needs maintainer API/product direction.
  • The PR still needs validation review for stale explicit paths and bare shell names before it would be safe to merge.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against 4878d3e059ce.

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

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant