Skip to content

Commit 5d919c1

Browse files
Fix Git Bash conda activation when conda init bash was run (microsoft#1370) (microsoft#1609)
Closes microsoft#1370 ## Context On Windows, when a user has run `conda init bash` and has `auto_activate_base` enabled (conda default), opening a Git Bash terminal in VS Code with auto-activation enabled causes every subsequent `conda` command to fail with: ``` bash: /cygdrive/c/Users/<user>/anaconda3/Scripts/conda.exe: No such file or directory ``` The terminal still displays `(base)` and appears active, but conda is silently broken until the user closes and reopens the terminal. ## Root cause A chain of three independent systems creates the failure: 1. The user's `~/.bashrc` (modified by `conda init bash`) initializes conda on shell startup — defines the `conda` shell function and sets `CONDA_EXE` correctly. 2. `auto_activate_base = true` activates `(base)`, which prepends `anaconda3/Library/usr/bin` to `PATH`. That directory ships **Anaconda's own (Cygwin-style) `cygpath`**, which now shadows Git Bash's MSYS `cygpath`. The two emit different path formats: - Git Bash's `cygpath C:\foo` → `/c/foo` ✅ (Git Bash understands) - Anaconda's `cygpath C:\foo` → `/cygdrive/c/foo` ❌ (Git Bash does NOT understand) 3. The extension then injects: ```bash source <conda>/etc/profile.d/conda.sh && conda activate <env> ``` The first line of `conda.sh` is: ```bash export CONDA_EXE="$(cygpath 'C:/.../conda.exe')" ``` Re-sourcing now runs the shadowed Cygwin `cygpath`, producing `/cygdrive/c/...`. `CONDA_EXE` is corrupted. The `conda` shell function (which delegates to `$CONDA_EXE`) fails for every subsequent invocation. The corruption is entirely caused by **re-sourcing `conda.sh` in a terminal that already initialized conda**. If we skip the re-sourcing, the original (correct) `CONDA_EXE` remains intact and conda keeps working. ## Why the existing safeguards do not catch this `buildShellActivationMapForConda` has a P1 short-circuit that skips re-sourcing when `isActiveOnLaunch` is true. That flag is derived from `process.env.CONDA_SHLVL` **at VS Code startup**. The user launches VS Code normally (not from a conda-active shell), so `CONDA_SHLVL` is unset at the VS Code process level and the safeguard never fires — even though conda will be active inside every new terminal. The extension already has a separate signal that *does* fire in this scenario: `checkCondaInitInShellProfiles()` reads the user's shell profiles and reports `shellInitStatus.bash = true` when `conda initialize` is present. The non-Windows path already accepts this signal, but only uses it as a fallback when sourcing scripts are missing. The Windows path doesn't accept it at all. ## The fix Pass `shellInitStatus` through to `windowsExceptionGenerateConfig` and add an early branch in the Git Bash logic: ```typescript if (shellInitStatus?.bash) { // .bashrc/.bash_profile already defines `conda` and sets CONDA_EXE // correctly. Re-sourcing conda.sh here would corrupt CONDA_EXE on // Windows when base is active (microsoft#1370). bashActivate = [{ executable: 'conda', args: ['activate', quotedPrefix] }]; } ``` The new branch is the **first** condition checked. When the user has run `conda init bash`, we emit only `conda activate <prefix>` instead of `source conda.sh && conda activate <prefix>`. ## Why this fixes the issue The bug is triggered by re-running `cygpath` (in a shadowed PATH context) inside `conda.sh`. By skipping the redundant `source`, we never re-execute that line, so `CONDA_EXE` retains the correct value set by the original `conda init bash` hook in the user's shell profile. The `conda activate` call then uses the already-loaded `conda` shell function with the correct `CONDA_EXE`, and everything works. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7e3f696 commit 5d919c1

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

src/managers/conda/condaUtils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ async function buildShellActivationMapForConda(
523523
envIdentifier,
524524
envManager.sourcingInformation.condaFolder,
525525
condaShPath,
526+
envManager.sourcingInformation.shellInitStatus,
526527
);
527528
return shellMaps;
528529
}
@@ -605,6 +606,7 @@ export async function windowsExceptionGenerateConfig(
605606
prefix: string,
606607
condaFolder: string,
607608
condaShPath?: string,
609+
shellInitStatus?: ShellCondaInitStatus,
608610
): Promise<ShellCommandMaps> {
609611
const shellActivation: Map<string, PythonCommandRunConfiguration[]> = new Map();
610612
const shellDeactivation: Map<string, PythonCommandRunConfiguration[]> = new Map();
@@ -625,7 +627,12 @@ export async function windowsExceptionGenerateConfig(
625627
// is bash-compatible; on Windows, sourceInitPath may point to "activate.bat", which
626628
// cannot be sourced by Git Bash, so in that case we skip emitting a Git Bash activation.
627629
let bashActivate: PythonCommandRunConfiguration[];
628-
if (condaShPath) {
630+
if (shellInitStatus?.bash) {
631+
traceVerbose(
632+
'Skipping `source conda.sh` for Git Bash because `conda init bash` was detected in the user shell profile',
633+
);
634+
bashActivate = [{ executable: 'conda', args: ['activate', quotedPrefix] }];
635+
} else if (condaShPath) {
629636
bashActivate = [
630637
{ executable: 'source', args: [condaShPath.replace(/\\/g, '/')] },
631638
{ executable: 'conda', args: ['activate', quotedPrefix] },

src/test/managers/conda/condaUtils.windowsActivation.unit.test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,133 @@ suite('Conda Utils - windowsExceptionGenerateConfig', () => {
118118
});
119119
});
120120

121+
suite('Git Bash activation when `conda init bash` was detected (#1370)', () => {
122+
test('Skips `source conda.sh` and emits only `conda activate <prefix>` when shellInitStatus.bash is true', async () => {
123+
getCondaHookPs1PathStub.resolves('C:\\conda\\shell\\condabin\\conda-hook.ps1');
124+
const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat';
125+
const prefix = 'myenv';
126+
const condaFolder = 'C:\\conda';
127+
const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh';
128+
129+
const result = await windowsExceptionGenerateConfig(sourceInitPath, prefix, condaFolder, condaShPath, {
130+
bash: true,
131+
});
132+
133+
const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH);
134+
assert.ok(gitBashActivation, 'Git Bash activation should be defined');
135+
assert.strictEqual(
136+
gitBashActivation.length,
137+
1,
138+
'Should have a single `conda activate` command when conda init bash is detected',
139+
);
140+
assert.strictEqual(gitBashActivation[0].executable, 'conda');
141+
assert.deepStrictEqual(gitBashActivation[0].args, ['activate', 'myenv']);
142+
});
143+
144+
test('Skips `source conda.sh` even when condaShPath is not provided', async () => {
145+
getCondaHookPs1PathStub.resolves(undefined);
146+
const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat';
147+
const prefix = 'myenv';
148+
const condaFolder = 'C:\\conda';
149+
150+
const result = await windowsExceptionGenerateConfig(sourceInitPath, prefix, condaFolder, undefined, {
151+
bash: true,
152+
});
153+
154+
const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH);
155+
assert.ok(gitBashActivation, 'Git Bash activation should be defined');
156+
assert.strictEqual(gitBashActivation.length, 1);
157+
assert.strictEqual(gitBashActivation[0].executable, 'conda');
158+
assert.deepStrictEqual(gitBashActivation[0].args, ['activate', 'myenv']);
159+
});
160+
161+
test('Quotes prefix paths that contain spaces', async () => {
162+
getCondaHookPs1PathStub.resolves(undefined);
163+
const prefixWithSpaces = 'C:\\Users\\John Doe\\envs\\myenv';
164+
165+
const result = await windowsExceptionGenerateConfig(
166+
'C:\\conda\\Scripts\\activate.bat',
167+
prefixWithSpaces,
168+
'C:\\conda',
169+
'C:\\conda\\etc\\profile.d\\conda.sh',
170+
{ bash: true },
171+
);
172+
173+
const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH);
174+
assert.ok(gitBashActivation);
175+
assert.strictEqual(gitBashActivation.length, 1);
176+
assert.strictEqual(gitBashActivation[0].executable, 'conda');
177+
assert.ok(gitBashActivation[0].args, 'args should be defined');
178+
assert.strictEqual(gitBashActivation[0].args[0], 'activate');
179+
assert.ok(
180+
gitBashActivation[0].args[1].startsWith('"') && gitBashActivation[0].args[1].endsWith('"'),
181+
'prefix containing spaces should be quoted',
182+
);
183+
});
184+
185+
test('Still emits `source conda.sh + conda activate` when shellInitStatus.bash is false', async () => {
186+
getCondaHookPs1PathStub.resolves(undefined);
187+
const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh';
188+
189+
const result = await windowsExceptionGenerateConfig(
190+
'C:\\conda\\Scripts\\activate.bat',
191+
'myenv',
192+
'C:\\conda',
193+
condaShPath,
194+
{ bash: false },
195+
);
196+
197+
const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH);
198+
assert.ok(gitBashActivation);
199+
assert.strictEqual(gitBashActivation.length, 2);
200+
assert.strictEqual(gitBashActivation[0].executable, 'source');
201+
assert.strictEqual(gitBashActivation[1].executable, 'conda');
202+
});
203+
204+
test('Still emits `source conda.sh + conda activate` when shellInitStatus is undefined', async () => {
205+
getCondaHookPs1PathStub.resolves(undefined);
206+
const condaShPath = 'C:\\conda\\etc\\profile.d\\conda.sh';
207+
208+
const result = await windowsExceptionGenerateConfig(
209+
'C:\\conda\\Scripts\\activate.bat',
210+
'myenv',
211+
'C:\\conda',
212+
condaShPath,
213+
);
214+
215+
const gitBashActivation = result.shellActivation.get(ShellConstants.GITBASH);
216+
assert.ok(gitBashActivation);
217+
assert.strictEqual(gitBashActivation.length, 2);
218+
assert.strictEqual(gitBashActivation[0].executable, 'source');
219+
assert.strictEqual(gitBashActivation[1].executable, 'conda');
220+
});
221+
222+
test('Does not affect PowerShell or CMD activation when shellInitStatus.bash is true', async () => {
223+
getCondaHookPs1PathStub.resolves('C:\\conda\\shell\\condabin\\conda-hook.ps1');
224+
const sourceInitPath = 'C:\\conda\\Scripts\\activate.bat';
225+
226+
const result = await windowsExceptionGenerateConfig(
227+
sourceInitPath,
228+
'myenv',
229+
'C:\\conda',
230+
'C:\\conda\\etc\\profile.d\\conda.sh',
231+
{ bash: true },
232+
);
233+
234+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
235+
assert.ok(pwshActivation);
236+
assert.strictEqual(pwshActivation.length, 2);
237+
assert.strictEqual(pwshActivation[0].executable, 'C:\\conda\\shell\\condabin\\conda-hook.ps1');
238+
assert.strictEqual(pwshActivation[1].executable, 'conda');
239+
240+
const cmdActivation = result.shellActivation.get(ShellConstants.CMD);
241+
assert.ok(cmdActivation);
242+
assert.strictEqual(cmdActivation.length, 2);
243+
assert.strictEqual(cmdActivation[0].executable, sourceInitPath);
244+
assert.strictEqual(cmdActivation[1].executable, 'conda');
245+
});
246+
});
247+
121248
suite('PowerShell activation', () => {
122249
test('Uses ps1 hook when available', async () => {
123250
// Arrange

0 commit comments

Comments
 (0)