Skip to content

fix(shellwrap): Git Bash on Windows cannot exec backslash-style paths - #1319

Closed
LocoLoboZ wants to merge 1 commit into
smart-mcp-proxy:mainfrom
LocoLoboZ:fix/gitbash-windows-backslash-path
Closed

LocoLoboZ wants to merge 1 commit into
smart-mcp-proxy:mainfrom
LocoLoboZ:fix/gitbash-windows-backslash-path

Conversation

@LocoLoboZ

Copy link
Copy Markdown
Contributor

Summary

  • Fixes Windows: Git Bash cannot exec backslash-style Windows paths, breaks every wrapper-script stdio server #1318: on Windows, when the resolved login shell is Git Bash / MSYS, WrapWithUserShell correctly single-quoted an absolute Windows path but Git Bash still couldn't execute it — MSYS's exec layer only resolves POSIX-style paths, so a backslash path falls through to bash's PATH lookup and fails with command not found (mangling the path in its own error output).
  • This broke every stdio server configured with an absolute Windows-path command (wrapper .cmd/.bat scripts are the common case) whenever $SHELL resolves to a bash-like shell on Windows — a common setup for anyone using Git Bash / MSYS2 / VS Code's integrated terminal as their default shell.
  • Fix: convert backslashes to forward slashes in the command and args before shell-escaping, but only on the Windows + bash-like-shell branch. Both CreateProcess and MSYS's exec layer accept forward-slash paths.
  • Updated TestSetupDockerIsolationShellWrapsWhenDaemonEnvMissingNonDarwin, which exercises the same shell-wrap code path for the Docker isolation fallback and was hitting the identical latent bug — its assertion now accepts either separator style.
  • Added TestWrapWithUserShell_GitBashCanExecWindowsPath, a Windows-only regression test that actually execs a real binary (whoami.exe) through the wrapped Git Bash command, so a regression here fails loudly instead of only failing a string-quoting assertion.

Test plan

  • go test ./internal/shellwrap/... — pass, including the new regression test
  • go test ./internal/upstream/... — pass (all subpackages)
  • go build ./... — clean
  • gofmt -l on changed files — clean
  • go vet ./internal/shellwrap/... ./internal/upstream/core/... — clean
  • Manual end-to-end: rebuilt mcpproxy with this change and reconnected a real config with 14 wrapper-.cmd-based stdio servers that were all failing with this exact error before the fix. All connected afterward — connected server count went from 13 to 27, tool count from 249 to 403.

WrapWithUserShell correctly single-quotes a backslash-style Windows path
(e.g. C:\ProgramData\foo\bar.cmd) when the resolved login shell is bash-like,
but Git Bash / MSYS still cannot execute it: MSYS's own exec layer only
resolves POSIX-style paths. A backslash path falls through to bash's PATH
lookup, which fails with "command not found" and mangles the path in its
own error rendering (backslashes silently dropped).

This breaks every stdio server configured with an absolute Windows-path
command (wrapper .cmd/.bat scripts are the common case) on any Windows host
where the resolved shell is bash-like, e.g. Git Bash / VS Code integrated
terminal / MSYS2 set as $SHELL.

Fix: convert backslashes to forward slashes in the command and args before
shell-escaping, but only on the Windows + bash-like-shell branch. Windows'
CreateProcess and MSYS's exec layer both accept forward-slash paths, so this
is safe in both directions.

Verified against a real config with 14 wrapper-.cmd-based stdio servers that
were all failing with this exact error: connected server count went from 13
to 27, tool count from 249 to 403, after rebuilding with this fix.

Fixes smart-mcp-proxy#1318
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 61.53846% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/shellwrap/shellwrap.go 61.53% 1 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@Dumbris Dumbris left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed writeup and the manual verification with 14 wrapper-.cmd servers — that's a great regression to have caught and a clear repro. The fix direction (stop keying the quoting dialect on GOOS and instead ask "what shell will actually parse this") is the right one.

I did find a few things worth another pass before merging, mostly around how broadly the new backslash→forward-slash conversion applies:

  1. toBashPath rewrites backslashes in every arg, not just paths (internal/shellwrap/shellwrap.go around the new closure). It's applied unconditionally to command and every element of args whenever GOOS=windows and the shell is bash-like, with no check that the string is actually a Windows path. That'll also mangle non-path values that happen to contain a literal \ — e.g. a SQL Server instance name like .\SQLEXPRESS, a DOMAIN\user credential, a regex (\d+\.log), or a Docker -e KEY=VALUE value injected via injectEnvVarsIntoDockerArgs — silently, with no error. Might be worth scoping the rewrite to things that look like Windows paths (e.g. ^[A-Za-z]:\\ or UNC \\) rather than any string containing \.

  2. The new regression test may not exercise the fix it's meant to guard. TestWrapWithUserShell_GitBashCanExecWindowsPath never sets $SHELL before calling WrapWithUserShell, and resolveLoginShell() reads $SHELL first — so on a Windows box/runner where $SHELL isn't set, it silently falls through to the cmd.exe branch instead of the Git Bash one, and the test would pass without ever touching the code path it's testing. On top of that, .github/workflows/unit-tests.yml only runs windows-latest on push, not on pull_request — so as written this test can't gate a PR either way. Could you add t.Setenv("SHELL", bashPath) and see if the Windows job can run on PRs (or is skipped intentionally for cost reasons)?

  3. Smaller / lower-confidence, worth a look:

    • isBashLikeShell's strings.Contains(lower, "sh") also matches pwsh.exe, fish, csh/tcsh. It previously only picked argv flags (-l -c vs /c); now it also gates the quoting dialect and the backslash rewrite, so a false-positive there (e.g. $SHELL=pwsh.exe) would now silently mis-quote/mis-convert rather than just fail loudly.
    • insertCidfileIntoShellDockerCommand's literal "docker run" substring match (internal/upstream/core/connection_docker.go) breaks once the docker path is quoted — which happens for the very common Docker Desktop default install path C:\Program Files\Docker\.... Pre-existing, but directly hit by this PR's target scenario, so flagging it here.
    • The public Shellescape() still keys its dialect on bare runtime.GOOS, unaware of isBash — its doc comment ("mirrors... so both code paths can converge") is now a bit stale, and a future caller of it could reintroduce this exact bug.

None of this is a knock on the diagnosis or the manual verification — the root cause and the general direction look right to me. Requesting changes mainly for #1 (the unscoped rewrite) and #2 (test coverage), since those affect correctness/confidence in the fix itself. Happy to take another look once addressed!

Dumbris added a commit that referenced this pull request Oct 5, 2026
…or take over PR #1319) (#1493)

## Summary

Fix stdio servers on Windows with Git Bash or MSYS shells failing to
execute commands due to backslash-style path handling. Converted paths
use forward slashes which are accepted by both Windows CreateProcess and
MSYS's exec layer.

- **#1318 — Git Bash stdio command execution**: Convert backslash paths
to forward slashes when wrapping commands for bash-like shells on
Windows. Fixed verified against 14 real wrapper-.cmd-based servers:
connected server count went from 13 to 27, tool count 249 to 403. Tests:
unit tests for path conversion logic, real Git Bash exec check.

- **Path conversion scoping**: Refined to convert only path-like
arguments (drive-letter and UNC paths), leaving regex patterns,
domain\user values and other backslash strings untouched. Tests:
comprehensive cases covering .cmd commands, paths with spaces, script
args, regex args, domain\user, UNC paths, and cmd.exe behavior.

- **Cross-platform test injection**: Injected GOOS into tests so Windows
code paths can be verified on any host (Linux/macOS) without running the
full integration. Tests: stubWindows() helper, non-Windows preservation
test.

Closes #1318

---------

Co-authored-by: Ploie77 <philippe.lopez@pm.me>
@Dumbris

Dumbris commented Oct 6, 2026

Copy link
Copy Markdown
Member

Thank you very much for this fix, @LocoLoboZ, and sorry it sat so long after the review.

Your diagnosis was right. Git Bash/MSYS on Windows cannot exec backslash-style paths, and converting them to forward slashes when wrapping commands for bash-like shells fixes it. We picked up your work and finished it in #1493 (commit 6d9caa9, now on main), and that commit credits you with a Co-authored-by trailer. The commit message reports the fix tested against 14 real .cmd-wrapper stdio servers: connected servers went from 13 to 27.

On top of your change, #1493 handles the review points from 2026-09-20:

  • Scoped conversion: only drive-letter and UNC path arguments are rewritten. Regex patterns, domain\user values and other strings with backslashes are left as they are.
  • Cross-platform tests: GOOS is injected (stubWindows()), so the Windows code paths run in CI on Linux and macOS, not only in the push-only Windows job.
  • More test cases: .cmd commands, paths with spaces, script arguments, regex arguments, domain\user, UNC paths and the cmd.exe behaviour.

Your fix is now in main and #1318 is closed, so this branch is superseded. It also conflicts with the newer version of shellwrap.go. We'll close this PR rather than merge it, with your contribution credited as above. We're sorry we finished it in a separate PR instead of on your branch. Thanks again, and we'd be glad to see more contributions from you.

@Dumbris Dumbris closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: Git Bash cannot exec backslash-style Windows paths, breaks every wrapper-script stdio server

3 participants