Skip to content

[Windows] exec uses cmd.exe for single-line and PowerShell for multi-line commands — inconsistent shell semantics #4544

Description

@chengyongru

Summary

On Windows, the exec tool routes single-line commands to cmd.exe (via asyncio.create_subprocess_shell) but multi-line commands to powershell. This split is inconsistent and cmd.exe's semantics are unfriendly to agents authoring cross-platform commands:

  • cd across drives silently fails (cmd needs cd /d)
  • POSIX-style expansion ($VAR, $(...)) is not supported and stays literal
  • The shell parameter is hard-rejected on Windows, so there is no escape hatch

The result is that single-line and multi-line commands behave differently, and cross-drive cd / $VAR workflows silently fail. Agents are forced into workarounds (writing logic to a .sh/.ps1 file and executing that instead).

Environment

  • OS: Windows
  • Source: nanobot/agent/tools/shell.py

Root cause (source analysis)

ExecTool._spawn chooses the shell purely on whether the command contains a newline:

# nanobot/agent/tools/shell.py — _spawn()
if _IS_WINDOWS:
    if "\n" in command:
        return await asyncio.create_subprocess_exec(
            "powershell", "-NoProfile", "-Command", command, ...)
    return await asyncio.create_subprocess_shell(command, ...)   # → cmd.exe /c
  • single-line → asyncio.create_subprocess_shellcmd.exe /c
  • multi-line (contains \n) → powershell -NoProfile -Command

The shell parameter is explicitly rejected on Windows:

# _resolve_shell()
if _IS_WINDOWS:
    return None, "Error: shell parameter is not supported on Windows"

_build_env (Windows branch) forwards a curated set of system variables but omits OS, so even %OS% stays literal under cmd.exe — making it look like no shell expands variables at all.

Reproduction

On Windows, invoke the agent's exec tool:

  1. cd "D:\some\dir" && pwd (cross-drive) → cd silently does not switch drives; pwd stays on the original drive. (cmd requires cd /d.)
  2. echo $HOME → outputs the literal $HOME (cmd does not expand POSIX-style variables).
  3. echo $(date) → literal (cmd has no command substitution).
  4. A multi-line version of the same operations behaves differently because it runs under PowerShell.
  5. shell="powershell"Error: shell parameter is not supported on Windows.

Impact

  • Agents authoring cross-platform commands hit silent failures (especially cross-drive cd).
  • Behavior diverges based on a single \n, which is hard to predict.
  • No escape hatch via the shell parameter, even when PowerShell (or Git-for-Windows bash / MSYS bash) is available on the system.

Suggested fix

A few non-exclusive options:

  • Allow the shell parameter on Windows (e.g. accept powershell, and/or bash when available).
  • Route single-line commands through PowerShell too, to unify single-/multi-line semantics.
  • If bash is detected (Git for Windows / MSYS), consider offering it as a selectable default.

Happy to open a PR if there's a preferred direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions