@@ -3854,3 +3854,165 @@ def test_parallel_workers_env_parsing_logic(self) -> None:
38543854 # Test fallback to default
38553855 value = int (os .environ .get ('CLAUDE_PARALLEL_WORKERS' , '3' ))
38563856 assert value == 3
3857+
3858+
3859+ class TestRunBashCommandMsysPathConversion :
3860+ """Tests for MSYS path conversion prevention in run_bash_command().
3861+
3862+ Git Bash (MSYS2) automatically converts POSIX-style paths like /c to
3863+ Windows drive paths like C:/. This breaks cmd.exe's /c flag which is
3864+ used to run commands. These tests verify that MSYS_NO_PATHCONV=1 is
3865+ set correctly to disable this conversion.
3866+ """
3867+
3868+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
3869+ @patch ('setup_environment.find_bash_windows' )
3870+ @patch ('subprocess.run' )
3871+ def test_msys_no_pathconv_set_on_windows (
3872+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
3873+ ) -> None :
3874+ """Verify MSYS_NO_PATHCONV=1 is set in env on Windows."""
3875+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
3876+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3877+
3878+ setup_environment .run_bash_command ('echo test' )
3879+
3880+ # Verify subprocess.run was called with env parameter containing MSYS_NO_PATHCONV
3881+ assert mock_run .called
3882+ call_kwargs = mock_run .call_args [1 ]
3883+ assert 'env' in call_kwargs
3884+ assert call_kwargs ['env' ].get ('MSYS_NO_PATHCONV' ) == '1'
3885+
3886+ @patch ('scripts.setup_environment.sys.platform' , 'linux' )
3887+ @patch ('shutil.which' )
3888+ @patch ('subprocess.run' )
3889+ def test_msys_no_pathconv_not_set_on_linux (
3890+ self , mock_run : MagicMock , mock_which : MagicMock ,
3891+ ) -> None :
3892+ """Verify MSYS_NO_PATHCONV is NOT set in env on Linux."""
3893+ mock_which .return_value = '/usr/bin/bash'
3894+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3895+
3896+ setup_environment .run_bash_command ('echo test' )
3897+
3898+ assert mock_run .called
3899+ call_kwargs = mock_run .call_args [1 ]
3900+ # On Linux, env should not contain MSYS_NO_PATHCONV
3901+ env = call_kwargs .get ('env' )
3902+ assert env is not None
3903+ assert 'MSYS_NO_PATHCONV' not in env
3904+
3905+ @patch ('scripts.setup_environment.sys.platform' , 'darwin' )
3906+ @patch ('shutil.which' )
3907+ @patch ('subprocess.run' )
3908+ def test_msys_no_pathconv_not_set_on_macos (
3909+ self , mock_run : MagicMock , mock_which : MagicMock ,
3910+ ) -> None :
3911+ """Verify MSYS_NO_PATHCONV is NOT set in env on macOS."""
3912+ mock_which .return_value = '/bin/bash'
3913+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3914+
3915+ setup_environment .run_bash_command ('echo test' )
3916+
3917+ assert mock_run .called
3918+ call_kwargs = mock_run .call_args [1 ]
3919+ # On macOS, env should not contain MSYS_NO_PATHCONV
3920+ env = call_kwargs .get ('env' )
3921+ assert env is not None
3922+ assert 'MSYS_NO_PATHCONV' not in env
3923+
3924+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
3925+ @patch ('setup_environment.find_bash_windows' )
3926+ @patch ('subprocess.run' )
3927+ def test_c_flag_preserved_in_command (
3928+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
3929+ ) -> None :
3930+ """Verify /c flag is preserved and not converted to C:/."""
3931+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
3932+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3933+
3934+ # Command that includes /c flag (typical Windows cmd wrapper)
3935+ setup_environment .run_bash_command ('cmd /c npx test-package' )
3936+
3937+ # Verify the command string passed to subprocess contains /c not C:/
3938+ assert mock_run .called
3939+ call_args = mock_run .call_args [0 ][0 ]
3940+ command_arg = call_args [- 1 ] # Last element is the command string
3941+ assert '/c' in command_arg
3942+ assert 'C:/' not in command_arg
3943+
3944+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
3945+ @patch ('setup_environment.find_bash_windows' )
3946+ @patch ('subprocess.run' )
3947+ @patch .dict ('os.environ' , {'EXISTING_VAR' : 'existing_value' , 'PATH' : '/usr/bin' })
3948+ def test_existing_env_vars_preserved (
3949+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
3950+ ) -> None :
3951+ """Verify existing environment variables are preserved when adding MSYS_NO_PATHCONV."""
3952+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
3953+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3954+
3955+ setup_environment .run_bash_command ('echo test' )
3956+
3957+ call_kwargs = mock_run .call_args [1 ]
3958+ env = call_kwargs .get ('env' )
3959+ assert env is not None
3960+ # MSYS_NO_PATHCONV should be set
3961+ assert env .get ('MSYS_NO_PATHCONV' ) == '1'
3962+ # Existing environment variables should be preserved
3963+ assert env .get ('EXISTING_VAR' ) == 'existing_value'
3964+ assert env .get ('PATH' ) == '/usr/bin'
3965+
3966+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
3967+ @patch ('setup_environment.find_bash_windows' )
3968+ @patch ('subprocess.run' )
3969+ def test_tilde_expansion_still_works (
3970+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
3971+ ) -> None :
3972+ """Verify that disabling path conversion does not break tilde expansion.
3973+
3974+ Tilde expansion (~) is handled by bash itself, not MSYS path conversion,
3975+ so it should continue to work when MSYS_NO_PATHCONV=1 is set.
3976+ """
3977+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
3978+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '/c/Users/test' , '' )
3979+
3980+ result = setup_environment .run_bash_command ('echo ~' )
3981+
3982+ # The command should execute successfully
3983+ assert mock_run .called
3984+ assert result .returncode == 0
3985+
3986+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
3987+ @patch ('setup_environment.find_bash_windows' )
3988+ @patch ('subprocess.run' )
3989+ def test_login_shell_with_msys_no_pathconv (
3990+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
3991+ ) -> None :
3992+ """Verify MSYS_NO_PATHCONV is set even with login_shell=True."""
3993+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
3994+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
3995+
3996+ setup_environment .run_bash_command ('echo test' , login_shell = True )
3997+
3998+ call_kwargs = mock_run .call_args [1 ]
3999+ env = call_kwargs .get ('env' )
4000+ assert env is not None
4001+ assert env .get ('MSYS_NO_PATHCONV' ) == '1'
4002+
4003+ @patch ('scripts.setup_environment.sys.platform' , 'win32' )
4004+ @patch ('setup_environment.find_bash_windows' )
4005+ @patch ('subprocess.run' )
4006+ def test_capture_output_with_msys_no_pathconv (
4007+ self , mock_run : MagicMock , mock_find_bash : MagicMock ,
4008+ ) -> None :
4009+ """Verify MSYS_NO_PATHCONV is set with capture_output=False."""
4010+ mock_find_bash .return_value = r'C:\Program Files\Git\bin\bash.exe'
4011+ mock_run .return_value = subprocess .CompletedProcess ([], 0 , '' , '' )
4012+
4013+ setup_environment .run_bash_command ('echo test' , capture_output = False )
4014+
4015+ call_kwargs = mock_run .call_args [1 ]
4016+ env = call_kwargs .get ('env' )
4017+ assert env is not None
4018+ assert env .get ('MSYS_NO_PATHCONV' ) == '1'
0 commit comments