Why Niubash

The long version of the pitch. The README sells it in five seconds; this page shows the receipts.

The Windows shell civil war

Every Windows developer knows the tabs open on their machine:

OptionThe fine print
CMDFrozen in 1987. Your company's docs still paste it.
PowerShellA genuinely powerful automation language. It is not Bash — your for loops, grep, and quoting instincts die on arrival.
WSLA whole Linux distribution as a hobby you didn't sign up for. Boot a VM to print a directory. Dock your Docker Desktop's memory. Greet /mnt/c/Users/you/....
Git Bash / MSYS2Brilliant emulation with a cost: it translates, mangles, and converts your paths at the worst possible moments, and every git pays an emulation tax.

The treaty

Niubash is one native Windows binary that runs Bash syntax on Windows paths with real Windows programs — and brings the Unix commands Windows never had (ls, cat, grep, find, test, printf, ...).

NiubashWSLGit BashPowerShell
Bash syntax✅✅✅❌
Native Windows paths (C:\..., no /mnt/c)✅❌⚠️ conversion quirks✅
Calls git.exe / node.exe directly✅⚠️ via /mnt/c⚠️ path translation✅
Unix commands (ls, grep, find)✅✅✅❌
Cold start to prompt~170 msseconds~1 s~280 ms
No extra OS, no VM✅❌✅✅
Themes, git prompt, plugins✅—✅⚠️

One binary. One process. No distro to patch, no emulation layer to appease.

PowerShell eats arguments

Windows itself is the root cause: the platform has no argv array. Every executable receives a single command-line string and parses it with its own rules — and PowerShell adds a second parser on top. That's how PowerShell/PowerShell#1995 ("Arguments for external executables aren't correctly escaped") became a household name, and how dotnet/runtime#23347 opens by citing "the ongoing quoting woes PowerShell experiences."

Same command line, two shells. Watch what actually reaches the process:

# PowerShell 5.1                                # Niubash
> node -e "console.log(JSON.stringify(          ❯ node -e "console.log(JSON.stringify(
    process.argv.slice(1)))" "a b" "" "c\"d"      process.argv.slice(1)))" "a b" "" "c\"d"
    "e\f" "---"                                   "e\f" "---"

ParserError: TerminatorExpectedAtEndOfString   ["a b","","c\"d","e\\f","---"]

That command is written the way any model trained on Bash would write it. PowerShell throws a parse error before the process even starts. And when you quote it PowerShell's own way, the damage is quieter but still real: five arguments in, two mangled ones out — the empty string vanishes, the embedded quote is flattened, the last two arguments fuse into one:

> node -e "console.log(JSON.stringify(process.argv.slice(1)))" "a b" "" 'c"d' "e\f" "---"
["a b","cd e\\f ---"]

Agent casualties, with receipts

For humans this is a nuisance. For AI agents it's a minefield: models are trained on Bash, their quoting instincts are Bash-shaped, and on a PowerShell machine every generated command is a roll of the dice.

The path philosophy: native in, native out

If your mental model says every Unix-flavored shell on Windows must contain a path-translation layer — because every one you've met so far did — that is the model running on precedent, not a fact about this product. In niubash, the Windows-native path is the first-class citizen: not the target of a conversion, but the representation the shell actually thinks in.

Windows-native binaries don't speak the MSYS dialect. Here is Git Bash, in the wild, failing at exactly the things Niubash does without thinking:

Git Bash failing: backslash paths eaten, /c/ paths rejected by cmd, MSYS path conversion breaking git grep

What just happened, command by command:

CommandIn Git Bash (MSYS)In Niubash
node -e "..." "C:\Users\you\repo"the backslashes are eaten — the argument vanishesargument arrives byte-for-byte
cmd /c dir /c/Windows/...cmd.exe rejects the MSYS path (exit 1)native path, native tools, no guessing
git grep "/fn main"MSYS rewrites /fn into a Windows path — the pattern silently dies/ is just a character, not a path to convert

The escape hatches are the confession

MSYS "solves" paths by guessing: it heuristically rewrites /c/foo into C:\foo, and famously mangles innocent arguments along the way — the notorious case of git grep "/pattern" silently becoming git grep "C:\...\pattern". A heuristic that always guesses right does not exist, which is precisely why Git for Windows ships MSYS_NO_PATHCONV and MSYS2 ships MSYS2_ARG_CONV_EXCL: an off switch for path conversion is a documented admission that the conversion layer breaks real commands. Under MSYS, every native tool call is a game of path roulette, and the house provides the abort switch next to the wheel.

Niubash keeps the boundary but changes its shape. The translation is deterministic and lives in niu's own Rubash executor, not in an emulator rewriting arguments behind the child's back: /d/repo reaches a native child as D:\repo, /nonexistent and /fn main pass through byte for byte, and the mapping does not check whether the path exists. The same principle holds for the rest of the path surface: the Windows-native path is not a format niubash converts into — it is the shell's internal representation. POSIX-style (/c/..., /usr/bin), WSL-style (/mnt/c/...), and Windows-style (C:\...) are input spellings that all resolve to that one native reality:

❯ cd /c/Users/you/repo     # MSYS-style input: understood
C:/Users/you/repo            # output: always Windows-native
❯ node -e "console.log(process.cwd())"
C:\Users\you\repo            # native binaries get native cwd
❯ cd "C:\Program Files"     # Windows-style input: understood
❯ pwd
C:/Program Files

Input in any dialect. Output always native. One representation, zero guessing: the executor's POSIX→native argument mapping is fixed and documented (windows-path-contract.md), and the one switch is __RUBASH_ARGV_DIALECT=legacy, which hands argv to the child unwritten — a dialect selector for a documented mapping, not the abort switch of a heuristic that might guess wrong. That is the honest version for your fingers, and for your agent.

The verdict

Every incumbent made a bet with the tools of its decade: CMD froze, PowerShell bet on a brand-new language, WSL bet on virtualization, and MSYS2/Cygwin bet on emulation. For the actual problem — running Bash on Windows, next to native Windows programs — three of those bets pay out nothing, and the fourth is structurally guaranteed to leak: translating between two path worlds is a guessing game, and guesswork embedded in infrastructure surfaces at the worst possible moment. The escape hatches (MSYS_NO_PATHCONV, MSYS2_ARG_CONV_EXCL) are the incumbents' own documentation of that fact.

The correct architecture is not a better emulator. It is no emulator: a native implementation where the Windows path is the only representation that exists, where Bash is implemented as a language — and proven against GNU Bash's own 83-suite upstream test corpus (82 suites byte-identical as of the 2026-09-25 ledger re-run, with the single nameref residual individually audited — see the rubash compatibility ledger) — and where native programs are called as what they are: native. That is what niubash ships, and it is why the emulation stack's failure modes are not "reduced" here. They are categorically absent.

Emulation was the previous century's approximation of this architecture. Niubash is the architecture.