← Agent directory / Universal guide
Wire any agent onto niubash
How an agent picks its shell follows patterns — work out its detection mechanism and you can wire it. Your agent never runs commands inside "the shell you are using": it picks its own, and on Windows that pick is decided by exactly four detection mechanisms. This page takes them apart one by one, then gives a three-step decision flow that works for agents the catalogue has never listed.
The four detection mechanisms
From agents.md, "The four mechanisms". The first three are wireable by you; the fourth is the hard limit behind every bare-name invocation — and the reason the other three exist. Each row's "how to wire it" lands on the matching copy block in the step-3 flow.
| Mechanism | Coverage | How to wire it |
|---|---|---|
| ① $SHELL · POSIX convention |
Agents that read the process environment — the largest class, including all
POSIX-style CLIs (opencode is the pure specimen: zero occurrences of
niu in the binary, no shell setting; $SHELL is the
whole mechanism).
Works automatically inside a niu session: niu exports
SHELL=<path to niu.exe>. But $SHELL is not
persisted — the same agent launched from the Start menu / Explorer sees an empty value. | Launch the agent inside niu (step 3 · try step 1 first), or persist SHELL into the user environment (block a). |
| ② per-agent explicit configuration |
Agents exposing a shell settings field / CLI flag:
Zed (terminal.shell.program), Claude Code
(CLAUDE_CODE_GIT_BASH_PATH), the VS Code family
(automationProfile), DSH (profile plugin).
Config-reading mechanisms are launch-method independent — set once, effective forever. | Find its shell setting via the section-4 self-check, then point it at niu.exe or the installer-placed bash.exe (block b is the universal VS Code family snippet). |
| ③ Git for Windows probe |
Agents with hard-coded Git paths, or that infer from git on PATH:
Claude Code (scans C:\Program Files\Git and the x86 directory first, then infers from git),
the whole VS Code family (Copilot / Windsurf / Trae / ZCode + the Cline, Roo Code, Kilo Code, Continue extensions).
It probes fixed locations: C:\Program Files\Git\bin\bash.exe and friends. | The niubash installer already places a bash.exe placeholder on those paths (x86 candidates and sh.exe included); run once as administrator (block c verifies). |
| ④ Bare-name resolution |
Callers that throw the bare name bash / sh to
PATH / ComSpec / PATHEXT.
The hard limit: in CreateProcess's search order the system directory (System32) comes before PATH — a bare
bash always hits C:\Windows\System32\bash.exe (WSL) first.
See agents.md, "Why PATH cannot win against System32". | A user PATH cannot outrank System32: either give a full path that lands on the Git probe (fold into ③), or make the caller use sh (no System32 namesake exists). |
The wiring decision flow (three steps)
Step 1 · Always try "launch from a niu session" first
This is the only approach that "just works" most of the time:
niu exports SHELL=<path to niu.exe>, and any agent
honoring the POSIX convention picks it up as-is. That is how the agents.md author runs:
process tree powershell.exe ← niu.exe ← opencode.exe, while opencode's shell
selector is literally
process.env.SHELL ?? which("bash") ?? "/bin/sh".
Zero cost — try it first.
# 1) Enter a niu session ($SHELL is exported here, pointing at niu.exe)
niu
# 2) Start your agent from inside the niu session (example):
opencode # or codex / any CLI agent
# 3) Have the agent run the verify command below, then match the output
# against step 2's signature table:
# echo "SHELL=$SHELL"; uname -s; uname -a echo "SHELL=$SHELL"; uname -s; uname -a
But $SHELL is not persisted: it exists only in descendant
processes of the niu session. The same agent launched from the Start menu / Explorer sees
an empty $SHELL and falls back to which("bash") — which on a
WSL-installed machine resolves to C:\Windows\System32\bash.exe, and suddenly
commands run inside Linux. The next two steps pin the result down.
Step 2 · Match the output against the signature table
Compare the three lines the agent produced with the table:
| You see | Actual shell | Next move |
|---|---|---|
uname -s = MSYS_NT-…, and uname -a ends with x86_64 Msys | niu (winuxcmd shim) | Success $SHELL points at niu.exe; add block a to pin it across launch methods. |
uname -s = MINGW64_NT-…, MSYSTEM=MINGW64 | Git Bash (Git for Windows' own runtime) | A probe-type mechanism won the race: go mechanism ③ (once the placeholders are in place, the probe hits niubash's bash.exe) or mechanism ② (explicit); persisting $SHELL also works as a floor. |
uname -s = Linux | WSL — the agent fell back to which("bash") and hit System32\bash.exe | The deepest silent wrong-shell hit: go mechanism ② or mechanism ③, and use mechanism ① (block a) to prevent the fallback. |
uname missing, $SHELL empty | PowerShell or cmd (tell them apart via $PSVersionTable.PSVersion vs %COMSPEC%) | Safe but not bash-compatible: go mechanism ② (for the VS Code family that is block b). |
The same signature check shows up in Cursor's own support thread: fixed means
uname -a ends with MINGW64_NT-… instead of
microsoft-standard-WSL2 — the niu target is MSYS_NT-…
(ending x86_64 Msys).
Step 3 · Configure per mechanism (three copy blocks)
# Persist SHELL into the user environment (same trick as the installer:
# one registry value, no elevation required)
# Adjust the path to your actual install location; default is %LOCALAPPDATA%\Programs\Niubash
[Environment]::SetEnvironmentVariable('SHELL', "$env:LOCALAPPDATA\Programs\Niubash\niu.exe", 'User')
# Undo (matches the installer's uninstall semantics):
# [Environment]::SetEnvironmentVariable('SHELL', $null, 'User')
# Note: only affects processes started afterwards — reopen your terminal / agent
The installer itself writes SHELL into the user environment too (removed on
uninstall) — item 1 of agents.md's "Recommended order of work": one registry value, no
elevation, covering the largest agent class (all POSIX-style CLIs) and making
$SHELL independent of the launch method. The command above is its manual twin.
// VS Code family settings.json (VS Code / Windsurf / Trae / ZCode, plus the
// extensions that run commands through the editor terminal or an automation
// profile: Cline, Roo Code, Kilo Code, Continue, ...)
// Swap path for your actual install; default %LOCALAPPDATA%\Programs\Niubash\niu.exe:
"terminal.integrated.automationProfile.windows": {
"path": "C:\\Users\\<your-user-name>\\AppData\\Local\\Programs\\Niubash\\niu.exe",
"args": []
}
Agents that shell out through an automation profile inherit this setting (the VS Code
family's explicit-control method). Beyond the placeholder paths, pointing the profile
straight at niu.exe skips one hop of the bash.exe launcher.
The Cursor exception: Cursor's IDE agent ignores
terminal.integrated.defaultProfile.windows and
automationProfile.windows and still runs PowerShell (workaround: enable the
Legacy Terminal Tool setting); its Agent CLI is hard-coded to PowerShell. See the
Cursor detail page.
# The installer's optional elevated step (run once as administrator) places the
# placeholders mechanism 3 needs:
# C:\Program Files\Git\bin\bash.exe <- Claude Code / VS Code family hard-coded probe 1
# C:\Program Files (x86)\Git\bin\bash.exe <- second candidate path
# C:\Program Files\Git\bin\sh.exe <- for tools that want sh next to bash
# Verify the placeholders (first line should print True; second should print MSYS_NT-...):
Test-Path 'C:\Program Files\Git\bin\bash.exe'
& 'C:\Program Files\Git\bin\bash.exe' -c 'uname -s'
These files are placed by the installer's optional elevated step — run
once as administrator (Program Files is not writable from a user PATH). One
cost, the whole family benefits: Claude Code's and the VS Code family's hard-coded probes
all land on niubash's launcher from then on. That bash.exe is not niu itself
but a launcher that starts niu.exe as a child
(agent → bash.exe → niu.exe): about 17.6 ms extra per
-c call (niubash#377); the historical "one console window flash per command"
problem (niubash#379) was fixed in rubash#668 — verify the flashes are gone once you upgrade.
Agent not in the catalogue? Ask three questions
The verified list is short; the agent ecosystem is not. Most agents hide their shell detection in one of three places — ask, in order:
-
One: check its settings
Go through its docs, settings UI and config schema looking for
shell,terminal,bash,profile; check--helpfor a--shell-style flag. Verified agents' knobs look like this: Zed →terminal.shell.program; Claude Code →CLAUDE_CODE_GIT_BASH_PATHin settings.json; the VS Code family →terminal.integrated.automationProfile.windows; DSH →dsh plugin --profile web add @cmx666/dsh-niu-bundle@<version>. Found one? That is mechanism ② — point it atniu.exeor the placeholderbash.exe. -
Two: check its code
If you can read the source or the binary, search for
detect/shell. Real specimens: Cursor'sdetectShellType(), Gemini CLI'sgetShellConfiguration(), Codex'scodex-rs/shell-command/src/shell_detect.rs, and the VS Code family's probe array buried inapp.asar(hard-codesC:\Program Files\Git\bin\bash.exeand the x86 candidate, then infers fromwhich("git")). What it reads tells you the mechanism — env var → ①; config field → ②; hard-coded Git path / git inference → ③; bare name on PATH → ④ — then wire per the section-2 table. -
Three: check whether it can be saved at all
If it unconditionally returns PowerShell (Gemini CLI today:
getShellConfiguration()has no bash branch on Windows), or hard-codes it (Cursor CLI today:$SHELL/$COMSPECboth ignored) — no configuration can save it; classify as "not supported yet" and wait for upstream to expose a shell path.
Figured it out? PRs welcome — add your findings to the /en/agents/ catalogue (agent name, mechanism class, configuration method, verify output): github.com/unixwin/unixwin.dev ↗. The "not verified here" list in agents.md is still long — Amazon Q Developer CLI, Goose, Amp, OpenHands, Devin… every bit of reconnaissance is a contribution.
Two things not to do while wiring (agents.md, "Two things not to do"):
first, never add winuxcmd\usr\bin to PATH — hundreds of names, many colliding
with System32; second, never try to replace C:\Windows\System32\bash.exe.
Known traps · four of them
-
MSYSTEM is empty
Inside a niu session
MSYSTEMis empty. Tools that sniff it to detect MSYS (e.g. the Gemini CLI community proposal) cannot see niu at all; setting it yourself is experimental — untested, and it changes how other MSYS-aware tools behave. Source: agents.md · Known traps, item 1 -
uname reporting MSYS_NT is deliberate compatibility
The winuxcmd shim's
unamereportsMSYS_NT-…— on purpose: any tool branching onunameclassifies the host as MSYS, which autotools-style tooling can literally observe. Do not "fix" it as a bug. Source: agents.md · Known traps, item 2 -
WSL bash silently runs Linux
A bare
bashthat resolves to the Windows WSL launcher starts bash inside Linux without a single error message — the agent quietly runs your commands on another operating system. That is where step 2'suname -s = Linuxrow comes from. Source: agents.md · Known traps, item 4 -
The System32 search order
When a bare
bashis handed to CreateProcess the search order is: app directory → current directory → system directory → Windows directory → PATH. The system directory always beats PATH, so a user-level PATH cannot outrankSystem32\bash.exe—subprocess(['bash', ...])demonstrably lands in WSL this way, whilecmd /c bashand PowerShell's& bashare resolved by the shell itself, where PATH rules. Source: agents.md · Why PATH cannot win against System32
Sources
-
Single source of truth: Coding agents on Windows, and the shell they run ↗
(the online copy of the niubash repo's
docs/src/agents.md) — this page's four mechanisms, verify signature table, path ownership and all traps come from that document. - Verification environment for that document: niu 1.4.0, niu-git 2.55.0.5 (flat layout), Git for Windows 2.50.1, a WSL2 distro installed; verified 2026-10-16.
- ← Back to the agent directory: opencode, Claude Code, Codex, the VS Code family, Zed, DSH, Cursor, Gemini CLI, Aider each have a dedicated swap page.
- Chinese edition: /agents/universal/