DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-win-bash

d

dsh-win-bash

Identified

Git Bash shell provider for DeepSeek Harness on Windows: ctx.shell + bash tool composed beside the pwsh stack

dsh-win-bash

English | 中文

Git Bash command execution for DeepSeek Harness on Windows.

What it does

The shipped DSH composition gates its two shell stacks by platform. On Windows the host composes @deepseek-ai/dsh-pwsh-sandbox (PowerShell) and every agent preset mounts @deepseek-ai/dsh-tool-pwsh; the bash stack stays disabled because dsh-bash-local spawns a bare bash resolved through PATH — which on Windows is C:\Windows\system32\bash.exe, the WSL launcher, not Git Bash. A WSL shell has a different filesystem view and no C:\-style paths, so it is not a drop-in Windows shell.

This plugin adds the missing piece:

  • a ctx.shell provider that spawns an explicitly resolved Git Bash executable (<git>\bin\bash.exe -c <command>), inheriting every upstream mechanic: managed process groups, timeout/abort classification, bounded output with spill files, the model-friendly environment (NO_COLOR, TERM=dumb, PAGER=cat), background jobs, and the file sandbox;
  • the shipped model-facing bash tool wired to that executor.

It composes beside the pwsh stack rather than replacing it: the loader row declares isolate: { shell: 'win-bash' }, which puts the executor and the tool into a private service realm. The host's ctx.shell (pwsh) is neither replaced nor duplicated, so the preset's pwsh tool keeps working untouched.

Install

dsh plugin --profile desktop add dsh-win-bash

From a local checkout, point at the directory instead:

dsh plugin --profile desktop add link:/path/to/dsh-win-bash

Either form writes the profile dependency and appends dsh-win-bash to dsh.profile.bundles; the plugin's own cordis.patch.yml supplies the loader row. The host picks the new bundle up on the next start (the profile reconciliation hot-reloads the row when the app is running).

To use it in another profile, repeat the command with that profile name.

Configuration

All knobs live on the win-bash row (override from the profile's cordis.patch.yml with a bare - id: win-bash / config: {...} entry — a patch replaces the row's whole config):

KeyDefaultMeaning
bashPath''Absolute path to bash.exe. Empty = auto-detect.
promptHinttrueAdd a one-line system-prompt section preferring bash over pwsh.
sandboxFallbackautoWhat a proven confined-spawn failure means on this host — see below.
timeoutMs120000Default foreground timeout (upstream default).
maxTimeoutMs600000Cap for per-call timeout overrides.
maxOutputBytes64000Per-stream in-memory cap; overflow spills to disk.
maxSpillBytes67108864Per-stream full-output spill cap.
graceMs3000Kill escalation and post-exit pipe-drain grace.
cwd(unset)Default working directory; the session workspace wins per call.

Confined Git Bash cannot initialize today (sandboxFallback)

On Windows, the upstream ACL runner (@deepseek-ai/dsh-sandbox-windows-acl) spawns confined children under a WRITE_RESTRICTED token, and Git Bash cannot initialize under it. Two routes to the same death — STATUS_DLL_INIT_FAILED, exit 0xC0000142, reported by the tool as (no output) [exit code: 3221225794] — are known:

  1. Console-less host. The runner spawns console children that inherit the host console (its own docs: "Console isolation is unavailable"; the CREATE_NO_WINDOW/CREATE_NEW_CONSOLE variants die under the restricted token). A DSH desktop app launched from Explorer/Start menu is a GUI process with no console anywhere in its tree, so console children die at DLL init. Reported from a real desktop install, where the shipped pwsh tool died the same silent way.
  2. msys shared object (every host). The msys runtime behind Git Bash creates \BaseNamedObjects\msys-* during init; the restricted token denies it (0xC0000022) and bash dies with a fatal-error stack trace on stderr. Reproduced with the real shipped runner on a dev machine: cmd.exe and powershell.exe survive the identical spawn (console or not) — only bash dies. Launching DSH from a console parent does NOT avoid this route.

The same command under danger-full-access works, because it never enters the sandbox path. So on Windows the fallback below is not a stopgap — it is the configuration that makes the bash tool work at all under a file-sandboxed session.

sandboxFallback decides what a proven init death means:

ValueBehavior
failExplicit, actionable error (root cause + remedies). Never runs unconfined.
unconfinedNever calls the sandbox: every command runs Git Bash directly, stamped enforcement: 'none'. No prompt — this is the operator's standing consent, for approver-less hosts (CI, headless).
auto (default)A read-only confined probe runs when the row activates: on a proven 0xC0000142 death every confined call — the first included — is answered with a sandbox denial and the command does not run. The shipped tool renders [sandbox: file access denied under <mode> mode] plus the escalation hint; the model retries the same command once with sandbox_permissions + a one-sentence justification, and the harness approval channel asks the user. Only an allowed-once outcome executes — unconfined, under the approved wider mode. If the probe is inconclusive, the first live confined call that carries the proven death latches the same denial behavior.

auto never bypasses a working sandbox: the denial only appears after a positive 0xC0000142 signature, which a confined child that actually ran cannot produce (cmd and pwsh survive the identical spawn; a command that crashes this way unconfined fails the same way, so asking the user is a safe diagnostic rather than a false positive). A terminal-launched dsh/dsh web keeps whatever works upstream — but per route 2 above, confined Git Bash is broken on Windows regardless of how the host was launched, so auto will legitimately deny there too.

What the user sees under auto: the first confined bash call comes back (no output) with the denial marker and the hint to retry the exact command once with sandbox_permissions (the narrowest wider mode that suffices) plus a justification — "the approval prompt asks the user". The retry raises an approval request (Allow this operation with <mode> permissions: …); on approval the command runs unconfined and the result's sandbox fact reads { mode: 'danger-full-access', denied: false } — an honest stamp, no bypass flag. A rejection throws "the user rejected escalating this command … it stays denied, so stop and explain instead of working around it". The host log carries a one-time win-bash: … warning naming the root cause. When the probe proved the death at row activation, the system-prompt section says so too, so the model asks for danger-full-access directly instead of stepping through the ladder one rung at a time.

Upstream (not this plugin): route 1 needs the runner to use DETACHED_PROCESS (or branch on GetConsoleWindow() === NULL) when the host has no console — the harness repo has no such branch today. Route 2 is not fixable by the runner at all: a WRITE_RESTRICTED token cannot grant the msys runtime object creation in \BaseNamedObjects, so confined Git Bash on Windows needs either this plugin's fallback or a different sandbox mechanism upstream.

Git Bash discovery order

  1. bashPath from the row config;
  2. the DSH_GIT_BASH_PATH environment variable;
  3. the registry (HKLM/HKCU/WOW6432Node SOFTWARE\GitForWindows → InstallPath) — this is how a version-stamped install like F:\ssdpath\Git\2331 is found;
  4. well-known install roots (%ProgramFiles%\Git, %ProgramFiles(x86)%\Git, %LOCALAPPDATA%\Programs\Git, Chocolatey, scoop, MSYS2);
  5. PATH, in order, skipping the WSL launcher under C:\Windows\System32.

An explicit bashPath (or the env var) is tried first without the WSL filter, so an operator who really wants WSL bash can point at it; it still has to exist, and an explicit path that does not exist falls through to the rest of the order (the dsh-pwsh-local behavior). Auto-detection never picks the WSL launcher, and resolves to nothing rather than guessing: the row then fails with an actionable message naming the fix.

Verify

In a session, the model (or you, through the tool list) can run:

bash: echo "$BASH_VERSION"; uname -s

MINGW64_NT-* means Git Bash; Linux would mean WSL. The Plugins page lists the row as win-bash (dsh-win-bash), and the host log prints win-bash: using Git Bash at <path> at startup.

Rollback

dsh plugin --profile desktop remove dsh-win-bash

or disable the row (plugin_manager → win-bash, or disabled: true in the profile patch). Either way the host returns to exactly its shipped state: the pwsh executor, the presets' pwsh tool, and no bash tool. Nothing else in the composition is touched, because the plugin never writes outside its own realm.

Known limitations

  • Windows only. The row is gated process.platform !== 'win32'; on POSIX the shipped bash stack already runs and a second bash tool would collide.
  • pwsh remains available. Removing it would mean restating an agent preset's whole plugin list, which silently drifts on every DSH upgrade (a renamed package there fails the preset mount for every session). The promptHint section steers the model to bash instead; sessions that should never see pwsh can use a preset without it.
  • Git Bash path semantics are MSYS semantics. /f/... paths, and Windows paths with backslashes may need quoting — ordinary Git Bash behavior, the same model-facing surface as any Git Bash user.
  • The confined sandbox path is inherited, not re-implemented. In workspace-write/read-only modes the upstream windows-acl runner wraps our argv; only the wrapped executable changes.
  • No Windows host can sandbox Git Bash today. Two independent routes kill the confined child before the command runs: a console-less GUI host (the runner's own documented limitation — launching DSH from a terminal avoids only this one), and the msys runtime's denied \BaseNamedObjects\msys-* object under the restricted token, which hits every host regardless of how DSH was launched. Both are upstream limitations of the ACL runner, not something the plugin can fix by choosing a different shell — pwsh dies the same way on the first route. Under sandboxFallback: auto the plugin answers such a call with a denial and lets the user authorize a wider mode.
  • Confined Git Bash is broken on every Windows host, not just desktops. Under the runner's restricted token the msys runtime cannot create its \BaseNamedObjects\msys-* shared object (0xC0000022 → bash dies 0xC0000142); reproduced with the real shipped runner, where cmd and pwsh survive the identical spawn. sandboxFallback is therefore the working configuration for Windows — under auto it routes every such call through user authorization instead of quietly running unconfined.

Comments

Loading…