From da5b5093d1faa4bf9fcf51a1ade66fab161572a5 Mon Sep 17 00:00:00 2001 From: Stella Huang Date: Tue, 23 Jun 2026 16:19:43 -0700 Subject: [PATCH] Skip `source conda.sh` for Git Bash on Windows when conda init bash is detected (#1370) When a user on Windows has run `conda init bash` and has `auto_activate_base = true`, re-sourcing `conda.sh` at terminal startup corrupts `CONDA_EXE`. The full Anaconda distribution ships its own Cygwin-style `cygpath` in `Library/usr/bin`, which gets prepended to PATH by base activation and shadows Git Bash's MSYS `cygpath`. `conda.sh`'s first line re-runs `cygpath` and emits a `/cygdrive/c/...` path, which Git Bash cannot resolve, so every subsequent `conda` command fails with `bash: /cygdrive/c/.../conda.exe: No such file or directory`. When `shellInitStatus.bash === true` (conda init detected in the user's .bash_profile/.bashrc), the `conda` shell function and `CONDA_EXE` are already loaded correctly when the terminal starts, so skip `source conda.sh` and emit only `conda activate `. - Add optional `shellInitStatus` parameter to `windowsExceptionGenerateConfig`. - Pass `envManager.sourcingInformation.shellInitStatus` from caller (mirrors what the non-Windows path already does). - Tests cover bash=true skips source, bash=false / undefined preserve existing behavior, prefix quoting still works, and PowerShell/CMD activation unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/managers/conda/condaUtils.ts | 9 +- .../condaUtils.windowsActivation.unit.test.ts | 127 ++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/src/managers/conda/condaUtils.ts b/src/managers/conda/condaUtils.ts index c73f63fb..49dffd63 100644 --- a/src/managers/conda/condaUtils.ts +++ b/src/managers/conda/condaUtils.ts @@ -523,6 +523,7 @@ async function buildShellActivationMapForConda( envIdentifier, envManager.sourcingInformation.condaFolder, condaShPath, + envManager.sourcingInformation.shellInitStatus, ); return shellMaps; } @@ -605,6 +606,7 @@ export async function windowsExceptionGenerateConfig( prefix: string, condaFolder: string, condaShPath?: string, + shellInitStatus?: ShellCondaInitStatus, ): Promise { const shellActivation: Map = new Map(); const shellDeactivation: Map = new Map(); @@ -625,7 +627,12 @@ export async function windowsExceptionGenerateConfig( // is bash-compatible; on Windows, sourceInitPath may point to "activate.bat", which // cannot be sourced by Git Bash, so in that case we skip emitting a Git Bash activation. let bashActivate: PythonCommandRunConfiguration[]; - if (condaShPath) { + if (shellInitStatus?.bash) { + traceVerbose( + 'Skipping `source conda.sh` for Git Bash because `conda init bash` was detected in the user shell profile', + ); + bashActivate = [{ executable: 'conda', args: ['activate', quotedPrefix] }]; + } else if (condaShPath) { bashActivate = [ { executable: 'source', args: [condaShPath.replace(/\\/g, '/')] }, { executable: 'conda', args: ['activate', quotedPrefix] }, diff --git a/src/test/managers/conda/condaUtils.windowsActivation.unit.test.ts b/src/test/managers/conda/condaUtils.windowsActivation.unit.test.ts index ef5c7d93..2e53fe9d 100644 --- a/src/test/managers/conda/condaUtils.windowsActivation.unit.test.ts +++ b/src/test/managers/conda/condaUtils.windowsActivation.unit.test.ts @@ -118,6 +118,133 @@ suite('Conda Utils - windowsExceptionGenerateConfig', () => { }); }); + suite('Git Bash activation when `conda init bash` was detected (#1370)', () => { + test('Skips `source conda.sh` and emits only `conda activate ` when shellInitStatus.bash is true', async () => { + getCondaHookPs1PathStub.resolves('C:\\conda\\shell\\condabin\\conda-hook.ps1'); + const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat'; + const prefix = 'myenv'; + const condaFolder = 'C:\\conda'; + const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh'; + + const result = await windowsExceptionGenerateConfig(sourceInitPath, prefix, condaFolder, condaShPath, { + bash: true, + }); + + const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH); + assert.ok(gitBashActivation, 'Git Bash activation should be defined'); + assert.strictEqual( + gitBashActivation.length, + 1, + 'Should have a single `conda activate` command when conda init bash is detected', + ); + assert.strictEqual(gitBashActivation[0].executable, 'conda'); + assert.deepStrictEqual(gitBashActivation[0].args, ['activate', 'myenv']); + }); + + test('Skips `source conda.sh` even when condaShPath is not provided', async () => { + getCondaHookPs1PathStub.resolves(undefined); + const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat'; + const prefix = 'myenv'; + const condaFolder = 'C:\\conda'; + + const result = await windowsExceptionGenerateConfig(sourceInitPath, prefix, condaFolder, undefined, { + bash: true, + }); + + const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH); + assert.ok(gitBashActivation, 'Git Bash activation should be defined'); + assert.strictEqual(gitBashActivation.length, 1); + assert.strictEqual(gitBashActivation[0].executable, 'conda'); + assert.deepStrictEqual(gitBashActivation[0].args, ['activate', 'myenv']); + }); + + test('Quotes prefix paths that contain spaces', async () => { + getCondaHookPs1PathStub.resolves(undefined); + const prefixWithSpaces = 'C:\\Users\\John Doe\\envs\\myenv'; + + const result = await windowsExceptionGenerateConfig( + 'C:\\conda\\Scripts\\activate.bat', + prefixWithSpaces, + 'C:\\conda', + 'C:\\conda\\etc\\profile.d\\conda.sh', + { bash: true }, + ); + + const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH); + assert.ok(gitBashActivation); + assert.strictEqual(gitBashActivation.length, 1); + assert.strictEqual(gitBashActivation[0].executable, 'conda'); + assert.ok(gitBashActivation[0].args, 'args should be defined'); + assert.strictEqual(gitBashActivation[0].args[0], 'activate'); + assert.ok( + gitBashActivation[0].args[1].startsWith('"') && gitBashActivation[0].args[1].endsWith('"'), + 'prefix containing spaces should be quoted', + ); + }); + + test('Still emits `source conda.sh + conda activate` when shellInitStatus.bash is false', async () => { + getCondaHookPs1PathStub.resolves(undefined); + const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh'; + + const result = await windowsExceptionGenerateConfig( + 'C:\\conda\\Scripts\\activate.bat', + 'myenv', + 'C:\\conda', + condaShPath, + { bash: false }, + ); + + const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH); + assert.ok(gitBashActivation); + assert.strictEqual(gitBashActivation.length, 2); + assert.strictEqual(gitBashActivation[0].executable, 'source'); + assert.strictEqual(gitBashActivation[1].executable, 'conda'); + }); + + test('Still emits `source conda.sh + conda activate` when shellInitStatus is undefined', async () => { + getCondaHookPs1PathStub.resolves(undefined); + const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh'; + + const result = await windowsExceptionGenerateConfig( + 'C:\\conda\\Scripts\\activate.bat', + 'myenv', + 'C:\\conda', + condaShPath, + ); + + const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH); + assert.ok(gitBashActivation); + assert.strictEqual(gitBashActivation.length, 2); + assert.strictEqual(gitBashActivation[0].executable, 'source'); + assert.strictEqual(gitBashActivation[1].executable, 'conda'); + }); + + test('Does not affect PowerShell or CMD activation when shellInitStatus.bash is true', async () => { + getCondaHookPs1PathStub.resolves('C:\\conda\\shell\\condabin\\conda-hook.ps1'); + const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat'; + + const result = await windowsExceptionGenerateConfig( + sourceInitPath, + 'myenv', + 'C:\\conda', + 'C:\\conda\\etc\\profile.d\\conda.sh', + { bash: true }, + ); + + const pwshActivation = result.shellActivation.get(ShellConstants.PWSH); + assert.ok(pwshActivation); + assert.strictEqual(pwshActivation.length, 2); + assert.strictEqual(pwshActivation[0].executable, 'C:\\conda\\shell\\condabin\\conda-hook.ps1'); + assert.strictEqual(pwshActivation[1].executable, 'conda'); + + const cmdActivation = result.shellActivation.get(ShellConstants.CMD); + assert.ok(cmdActivation); + assert.strictEqual(cmdActivation.length, 2); + assert.strictEqual(cmdActivation[0].executable, sourceInitPath); + assert.strictEqual(cmdActivation[1].executable, 'conda'); + }); + }); + suite('PowerShell activation', () => { test('Uses ps1 hook when available', async () => { // Arrange