Runs DSH's stock bash tool on Git Bash for Windows, so commands go straight to bash -c instead of being re-parsed by the pwsh tool.
Install
# from a prebuilt release tarball
dsh plugin --profile web add "https://github.com/Fishquito7/dsh-gitbash/releases/latest/download/dsh-git-bash.tgz"
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Fishquito7/dsh-gitbash
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
What this plugin solves
On Windows, DSH hard-wires its shell to PowerShell. There is no bash tool in the catalog at all, so whenever a model wants to run bash it has to go the long way around:
# The reality: the command has to survive two parsers
& "C:\Program Files\Git\bin\bash.exe" -lc "echo \"dollar=\$HOME\" ; ls *.ts | head -3"
Quotes, backslashes, $ and single quotes all mean different things in the two dialects, so getting it right is largely luck. After installing this plugin, the same command looks like this:
echo "dollar=$HOME"; ls *.ts | head -3
The command string goes straight to bash -c and is parsed by bash rules exactly once.
Four advantages
| # | Advantage | Details |
|---|---|---|
| 1 | No new preset required | Install once at the profile layer; ptc, standard and cordis presets stay untouched and every session picks it up. |
| 2 | Lands directly in the callable tool list | Native presentation gains a bash function definition; PTC presentation automatically gains a tools.bash(...) binding. No wrapper tool to write. |
| 3 | No extra escaping, bash called directly | The pwsh -Command hop disappears, and with it a whole class of accidents: $(...), globs, single-quoted literals and backslashes all behave as bash defines them. |
| 4 | The model chooses bash or PowerShell | Both coexist without interfering: commands, pipes, grep/sed/find and scripts go to bash; the registry, services, process handles and native Windows paths stay on pwsh. |
Before and after
| Before | After | |
|---|---|---|
| Tool catalog | pwsh only (bash is disabled by a platform gate) |
pwsh + bash |
| Path to bash | pwsh → bash (two parsers) | bash (one parser) |
| Preset changes | — | None; install once at the profile layer |
| Effect on pwsh | — | None, it keeps working exactly as before |
Install
Replace
webwith the profile you actually use (the Web UI ships one namedweb).
Option 1: GitHub Release tarball
dsh plugin --profile web add "https://github.com/Fishquito7/dsh-gitbash/releases/latest/download/dsh-git-bash.tgz"
releases/latest/download/ always points at the newest release. To pin a version, use the tag-qualified URL:
https://github.com/Fishquito7/dsh-gitbash/releases/download/v0.1.0/dsh-git-bash-0.1.0.tgz
Fallback: install straight from the GitHub repository
dsh plugin --profile web add "github:Fishquito7/dsh-gitbash"This path goes through pnpm directly, and some environments refuse it under their build-script policy (pnpm reports
ignored build scriptsand similar). If that happens, go back to Option 1.
Option 2: local install
You already downloaded the tarball, or you want to install a local source directory:
# a local tarball
dsh plugin --profile web add "file:C:/path/to/dsh-git-bash-0.1.0.tgz"
# a local source directory (while editing)
dsh plugin --profile web add "file:C:/path/to/dsh-gitbash"
Restart DSH
A newly installed bundle is a new module root and is not hot-reloaded, so the bash tool appears only after a host restart.
Verify
After the restart, the plugin panel should show git-bash-executor and git-bash-tool as running. Then ask the model to run:
echo "argv0=$0"; uname -s; echo "bash=$BASH_VERSION"
# expected: /usr/bin/bash, MINGW64_NT-..., bash 5.x
Upgrade
pnpm will not re-fetch a dependency it already has, so upgrade by removing first:
dsh plugin --profile web remove dsh-git-bash
dsh plugin --profile web add "https://github.com/Fishquito7/dsh-gitbash/releases/latest/download/dsh-git-bash.tgz"
Then restart DSH. To pin a version, swap the URL for the tag-qualified form.
Uninstall and rollback
dsh plugin --profile web remove dsh-git-bash
# then restart DSH
If anything misbehaves, you can instead switch off just the git-bash-executor and git-bash-tool rows in the panel and restart — the tool and the executor are separable.
Configuration
| Item | Meaning |
|---|---|
bashPath (row config) |
Explicit absolute path to bash.exe; highest priority |
DSH_GIT_BASH |
The same override through the environment |
Row config |
All upstream dsh-bash-local fields are accepted: cwd, timeoutMs, maxTimeoutMs, maxOutputBytes, maxSpillBytes, graceMs |
No configuration is needed. Git can live almost anywhere depending on install flavour (machine-wide, per-user, scoop, chocolatey), so the plugin looks for it in order:
row
configbashPath→ environmentDSH_GIT_BASH→ theInstallPathvalue underHKLM/HKCUSOFTWARE\GitForWindows→%ProgramFiles%\Git\bin→%ProgramFiles(x86)%→%ProgramW6432%→%LOCALAPPDATA%\Programs\Git\bin(per-user installs) → scoop → chocolatey →bash.exein each PATH entry, plus..\bin\bash.exenext to anygit.exe.That last one matters: Git's installer puts
Git\cmdon PATH and neverGit\bin— which is exactly why a barebashis not found on Windows.If every candidate fails, the plugin does not fall back to WSL's
bash(a Linux process in another kernel, wrong semantics); it reports an error listing every path it tried.
How it works
In one sentence: a tool only advertises a capability; the thing that actually runs the command is the ctx.shell service — and a process can bind ctx.shell only once. So we open a separate isolate isolation scope and put a second binding in it.
Three facts unpack that:
1. A tool advertises; it does not execute.
A tool is essentially a specification: its name, its parameters, how its result is rendered. All it does internally is hand the command over with ctx.shell.run(...). It has no idea whether bash or PowerShell sits underneath.
2. ctx.shell is what really runs commands, and it is hard-wired.
shell is a service in DSH. On Windows it is hard-wired to the PowerShell executor, and the bash tool itself is switched off by a platform gate — which is why pwsh is the only shell tool in the catalog.
On top of that, one DSH process allows exactly one implementation per service name; hanging a second one on the same name fails loudly. That is why you cannot simply "add a bash".
3. Our approach: an isolate isolation scope holds the second binding.
isolate is how a composition declares an isolation scope (picture a small box): inside that scope the name shell is bound to a different implementation — the name is unchanged, only the key behind it differs — and everything outside the scope is untouched.
Inside the isolation scope we mount two things:
- an executor that hands commands to Git Bash — added by this plugin, about 30 lines of core code;
- the stock upstream bash tool — zero changes. It still just calls
ctx.shell.run(...); inside the isolation scope, that call lands on Git Bash.
host (untouched) tool-pwsh ──► ctx.shell (host scope) = PowerShell executor
isolate scope tool-bash ──► ctx.shell (isolation scope) = Git Bash executor ← this plugin
↑ upstream code, unchanged ↑ same service name, two isolate bindings
The result is two tools, two bindings, no interference: the stock tool-pwsh points at the host ctx.shell (the PowerShell executor), while the tool-bash row we added points at the isolation-scope ctx.shell (the Git Bash executor) — the same tool code, bound to different isolates, yields two different shells.
Why not simply rewrite a bash tool? Because the upstream tool carries a lot of logic: argument validation, timeouts, output truncation and spill files, background jobs and their wiring, [stderr] and [exit code: N] rendering. Since it is designed to know only ctx.shell and nothing about any specific shell, replacing the service under its feet turns it into a bash tool automatically — a few hundred lines saved, and zero drift from upstream.
Boundaries and known limitations
- No file sandboxing: commands run with the DSH process authority and never produce
[sandbox: file access denied]. The reason is practical — Git Bash is an MSYS2 process that needs fork, pipes and a private temp directory, which behaves unreliably under the Windows restricted token, so sandboxing is deliberately not wired in. - Bound to one choke point, not to upstream entry-point names:
dsh-bash-local's subclass entry points have already been renamed once (run/start+runArgv/startArgv→execute/executeArgv), yet every execution path still funnels throughspawnSpec(spec, argv, …)to turn an argv into a spawn config. The plugin swaps the barebashfor the resolved Git Bash exactly there, so another rename of an entry point cannot silently fall back to barebash; the named entry points are overridden as a second layer. On first use it also walks the public path (resolve→execute→result) and logs the shell identity — should upstream ever move the choke point too, the log saysself-check FAILEDinstead of letting command output come back garbled. - Read-only, no environment changes: resolution only reads the registry and reverse-engineers
PATH; it never editsPATH, the registry or DSH itself, so a DSH upgrade needs no reconfiguration. file:dependencies are copies, not symlinks: after editing the code you mustremove→add→ restart for the change to take effect.- Windows only: on POSIX platforms DSH already ships a bash tool, so this plugin has nothing to add.
FAQ
Q: The panel shows an executor row and a tool row — do I need both? Yes. The executor provides the shell service; the tool consumes it. Turning off only the executor makes the bash tool disappear (it becomes pending rather than erroring).
Q: The tool did not show up after installing.
Check in order: (1) did you restart DSH; (2) are both rows running; (3) does dsh --profile web --dump-config exit cleanly.
Q: Does it replace pwsh?
No. The isolation scope only affects the single name shell, and only inside the isolation scope. Both tools are verified to work side by side.
Q: Why Git Bash by default, and not WSL bash?
Because Git Bash is a native Windows process, while WSL bash is a Linux process in another kernel. For "run a command inside a Windows workspace", the former is simply the right tool:
| Git Bash (this plugin) | WSL bash | |
|---|---|---|
| Process | native Windows process (MSYS2 runtime) | Linux process on the WSL kernel |
| Workspace path | C:\...\proj ↔ /c/.../proj, the same directory |
must be written /mnt/c/...; $HOME is a Linux home |
DSH file tools (read/edit/glob) |
operate on the same files, one path model | split path model, the model must translate paths |
| File I/O | native NTFS | /mnt/c goes through a forwarding layer; small-file work is notably slower |
| Real Linux toolchain | ✗ no fork / apt / systemd, and MSYS rewrites path-looking arguments | ✅ complete |
In one line: working in a Windows workspace → Git Bash; needing a real Linux environment → run the whole DSH inside WSL (then the shell, the file tools and the paths are all Linux-native and consistent). "DSH on Windows plus WSL bash" is the worst of both: the shell lives in Linux while the file tools live in Windows.
This also explains why upstream disables dsh-bash-local outright on win32: a bare bash has no well-defined meaning on Windows — it may be the WSL launcher, or it may not exist at all.
Q: Git is not installed on C:, or lives somewhere unusual — will the plugin find it?
Yes. The first link in the chain has nothing to do with install location: Git for Windows registers itself under HKLM\SOFTWARE\GitForWindows (HKCU for per-user installs) in the InstallPath value. The full order is in the Configuration section above. If nothing is found, the error lists every path that was tried and tells you how to set bashPath or DSH_GIT_BASH explicitly.
Q: Can I switch to a different bash?
Set the DSH_GIT_BASH environment variable to any bash executable.
Development
dsh-gitbash/ # repository name; the package stays dsh-git-bash
├─ cordis.patch.yml # composition patch: isolation scope + two rows
├─ lib/index.js # the executor plugin (~30 lines of core code)
├─ test/harness.mjs # offline harness driven by a real cordis, no DSH boot needed
├─ package.json # zero dependencies
└─ README.md
Offline verification (the package must already sit on a profile resolution chain, e.g. installed under $DSH_HOME/profiles/web/node_modules/):
node test/harness.mjs
The harness checks that the host shell was not replaced, that the isolation scope resolves to this executor, that no settings section is registered twice, that the shell really is Git Bash, that globs and $(...) expand, that exit codes propagate, that stderr is separated, that the spawnSpec choke point still swaps a bare bash for Git Bash (the path taken after an upstream entry-point rename), and what the first-use self-check reports.
Packaging:
npm pack # produces dsh-git-bash-0.1.0.tgz
License
Links
More in this category
DayuanJiang/next-ai-draw-io#dsh-plugin★ 36718
Draws draw.io diagrams in DeepSeek Harness through the next-ai-drawio MCP server: the link in the reply opens a live preview in the right sidebar, you can edit the diagram by hand, and each AI change is a version you can restore. Node.js 18+; npx downloads the MCP server on first use.
Tencent/WeKnora#dsh-weknora★ 36302
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 7042
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
T-Auto/dsh-ops★ 5074
Provides bash, PowerShell 7, and high-performance Rust tools for DSH on Windows to reduce token usage.
sixtysevenlf/dsh-blender-plugin★ 1575
Lets a DSH agent drive a local Blender over a direct TCP channel with 15 tools: viewport frames, custom-angle renders, Python execution, headless and job offload, transaction rollback, and a planning layer of 228 ops across 34 families.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1155
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.