.github/skills/python-manager-discovery/SKILL.md
Environment manager-specific discovery patterns and known issues. Use when working on or reviewing environment discovery code for conda, poetry, pipenv, pyenv, or venv.
npx skillsauth add microsoft/vscode-python-environments python-manager-discoveryInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill documents manager-specific discovery patterns, environment variable precedence, and known issues.
| Manager | Config Files | Cache Location | Key Env Vars |
| ------- | ---------------------------------------------- | ------------------------ | --------------------------------------------------- |
| Poetry | poetry.toml, pyproject.toml, config.toml | Platform-specific | POETRY_VIRTUALENVS_IN_PROJECT, POETRY_CACHE_DIR |
| Pipenv | Pipfile, Pipfile.lock | XDG or WORKON_HOME | WORKON_HOME, XDG_DATA_HOME |
| Pyenv | .python-version, versions/ | ~/.pyenv/ or pyenv-win | PYENV_ROOT, PYENV_VERSION |
| Conda | environment.yml, conda-meta/ | Registries + paths | CONDA_PREFIX, CONDA_DEFAULT_ENV |
| venv | pyvenv.cfg | In-project | None |
Virtualenvs cache (default):
%LOCALAPPDATA%\pypoetry\Cache\virtualenvs~/Library/Caches/pypoetry/virtualenvs~/.cache/pypoetry/virtualenvsIn-project (when enabled):
.venv/ in project rootpoetry.toml in project rootPOETRY_VIRTUALENVS_*~/.config/pypoetry/config.toml| Issue | Description | Fix |
| ------------------------- | ----------------------------------------------- | -------------------------------- |
| {cache-dir} placeholder | Not resolved in paths from config | Resolve placeholder before use |
| Wrong default path | Windows/macOS differ from Linux | Use platform-specific defaults |
| In-project detection | POETRY_VIRTUALENVS_IN_PROJECT must be checked | Check env var first, then config |
async function getPoetryVirtualenvsPath(): Promise<string> {
// 1. Check environment variable first
const envVar = process.env.POETRY_VIRTUALENVS_PATH;
if (envVar) return envVar;
// 2. Check local poetry.toml
const localConfig = await readPoetryToml(projectRoot);
if (localConfig?.virtualenvs?.path) {
return resolvePoetryPath(localConfig.virtualenvs.path);
}
// 3. Use platform-specific default
return getDefaultPoetryCache();
}
function resolvePoetryPath(configPath: string): string {
// Handle {cache-dir} placeholder
if (configPath.includes('{cache-dir}')) {
const cacheDir = getDefaultPoetryCache();
return configPath.replace('{cache-dir}', cacheDir);
}
return configPath;
}
Default:
~/.local/share/virtualenvs/ (XDG_DATA_HOME)~/.local/share/virtualenvs/~\.virtualenvs\When WORKON_HOME is set:
$WORKON_HOME/ directly| Var | Purpose |
| ------------------------ | ---------------------------- |
| WORKON_HOME | Override virtualenv location |
| XDG_DATA_HOME | Base for Linux default |
| PIPENV_VENV_IN_PROJECT | Create .venv/ in project |
| Issue | Description | Fix | | ----------------------------- | ------------------- | ---------------------------- | | Missing WORKON_HOME support | Env var not checked | Read env var before defaults | | Missing XDG_DATA_HOME support | Not used on Linux | Check XDG spec |
function getPipenvVirtualenvsPath(): string {
// Check WORKON_HOME first
if (process.env.WORKON_HOME) {
return process.env.WORKON_HOME;
}
// Check XDG_DATA_HOME on Linux
if (process.platform === 'linux') {
const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
return path.join(xdgData, 'virtualenvs');
}
// Windows/macOS defaults
return path.join(os.homedir(), '.virtualenvs');
}
Unix:
~/.pyenv/versions/ (default)$PYENV_ROOT/versions/ (if PYENV_ROOT set)Windows (pyenv-win):
%USERPROFILE%\.pyenv\pyenv-win\versions\| Aspect | Unix | Windows (pyenv-win) |
| ------- | ----------------- | --------------------------------------- |
| Command | pyenv | pyenv.bat |
| Root | ~/.pyenv/ | %USERPROFILE%\.pyenv\pyenv-win\ |
| Shims | ~/.pyenv/shims/ | %USERPROFILE%\.pyenv\pyenv-win\shims\ |
| Issue | Description | Fix |
| ---------------------------------- | ------------------------------------------ | ---------------------------------- |
| path.normalize() vs path.resolve() | Windows drive letter missing | Use path.resolve() on both sides |
| Wrong command on Windows | Looking for pyenv instead of pyenv.bat | Check for .bat extension |
function getPyenvRoot(): string {
if (process.env.PYENV_ROOT) {
return process.env.PYENV_ROOT;
}
if (process.platform === 'win32') {
// pyenv-win uses different structure
return path.join(os.homedir(), '.pyenv', 'pyenv-win');
}
return path.join(os.homedir(), '.pyenv');
}
function getPyenvVersionsPath(): string {
const root = getPyenvRoot();
return path.join(root, 'versions');
}
// Use path.resolve() for comparisons!
function comparePyenvPaths(pathA: string, pathB: string): boolean {
return path.resolve(pathA) === path.resolve(pathB);
}
Environment locations:
envs/ directory~/.conda/envs/~/.condarc envs_dirsWindows Registry:
HKCU\Software\Python\ContinuumAnalytics\HKLM\SOFTWARE\Python\ContinuumAnalytics\| Shell | Activation Command |
| ---------- | -------------------------------------- |
| bash, zsh | source activate envname |
| fish | conda activate envname (NOT source!) |
| PowerShell | conda activate envname |
| cmd | activate.bat envname |
| Issue | Description | Fix | | --------------------- | ----------------------- | -------------------------- | | Fish shell activation | Uses bash-style command | Use fish-compatible syntax | | Registry paths | May be stale/invalid | Verify paths exist | | Base vs named envs | Different activation | Check if activating base |
function getCondaActivationCommand(shell: ShellType, envName: string): string {
switch (shell) {
case 'fish':
// Fish uses different syntax!
return `conda activate ${envName}`;
case 'cmd':
return `activate.bat ${envName}`;
case 'powershell':
return `conda activate ${envName}`;
default:
// bash, zsh
return `source activate ${envName}`;
}
}
Identification:
pyvenv.cfg file in directoryhome and optionally version keysversion field in pyvenv.cfghome path (e.g., Python311)async function getVenvVersion(venvPath: string): Promise<string | undefined> {
const cfgPath = path.join(venvPath, 'pyvenv.cfg');
try {
const content = await fs.readFile(cfgPath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const [key, value] = line.split('=').map((s) => s.trim());
if (key === 'version') {
return value;
}
}
// Fall back to parsing home path
const homeLine = lines.find((l) => l.startsWith('home'));
if (homeLine) {
const home = homeLine.split('=')[1].trim();
const match = home.match(/(\d+)\.(\d+)/);
if (match) {
return `${match[1]}.${match[2]}`;
}
}
} catch {
// Config file not found or unreadable
}
return undefined;
}
The PET server is a Rust-based locator that communicates via JSON-RPC over stdio.
| Issue | Description | Fix | | ------------------- | -------------------------------- | ----------------------------- | | No timeout | JSON-RPC can hang forever | Add Promise.race with timeout | | Silent spawn errors | Extension continues without envs | Surface spawn errors to user | | Resource leaks | Worker pool not cleaned up | Dispose on deactivation | | Type guard missing | Response types not validated | Add runtime type checks | | Cache key collision | Paths normalize to same key | Use consistent normalization |
async function fetchFromPET<T>(method: string, params: unknown): Promise<T> {
const timeout = 30000; // 30 seconds
const result = await Promise.race([
this.client.request(method, params),
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('PET server timeout')), timeout)),
]);
// Validate response type
if (!isValidResponse<T>(result)) {
throw new Error(`Invalid response from PET: ${JSON.stringify(result)}`);
}
return result;
}
development
VS Code settings precedence rules and common pitfalls. Essential for any code that reads or writes settings. Covers getConfiguration scope, inspect() vs get(), and multi-workspace handling.
tools
Run smoke tests to verify extension functionality in a real VS Code environment. Use this when checking if basic features work after changes.
development
Run the mandatory pre-commit checks before committing code. Includes lint, type checking, and unit tests. MUST be run before every commit.
tools
Run integration tests to verify that extension components work together correctly. Use this after modifying component interactions or event handling.