From e0fcdf1c3f2ecada2010bfa90034db857b2d2d56 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 16 Jun 2026 11:55:54 +0200 Subject: [PATCH] Demonstrate that shell metacharacters are mangled on Windows On Windows, NewShell escapes shell metacharacters (`&`, `|`, `<`, `>`, `%`) with `^` and splits the command into separate arguments. The operators in a custom command therefore never reach cmd as operators, so command chaining (`&&`), pipes, redirection and `%VAR%` expansion all silently break (#2427, #4147, #5113; the stray `^` is also what #3092 reports). Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/commands/oscommands/os_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/commands/oscommands/os_test.go b/pkg/commands/oscommands/os_test.go index 33bb9a6db..e15e4de2d 100644 --- a/pkg/commands/oscommands/os_test.go +++ b/pkg/commands/oscommands/os_test.go @@ -83,6 +83,24 @@ func TestOSCommandQuoteWindows(t *testing.T) { assert.EqualValues(t, expected, actual) } +// On Windows, NewShell must hand the command to cmd.exe verbatim. +func TestNewShellWindowsPassesMetacharactersVerbatim(t *testing.T) { + osCommand := NewDummyOSCommand() + platform := &Platform{OS: "windows", Shell: "cmd", ShellArg: "/c"} + osCommand.Platform = platform + osCommand.Cmd.platform = platform + + command := `echo a && echo b | sort > out.txt < in.txt %PATH%` + + assert.Equal(t, + /* EXPECTED: + []string{"cmd", "/s", "/c", command}, + ACTUAL: */ + []string{"cmd", "/c", "echo", "a", "^&^&", "echo", "b", "^|", "sort", "^>", "out.txt", "^<", "in.txt", "^%PATH^%"}, + osCommand.Cmd.NewShell(command, "").Args(), + ) +} + func TestOSCommandFileType(t *testing.T) { type scenario struct { path string