| name | python-manager-discovery |
| description | Environment manager-specific discovery patterns and known issues. Use when working on or reviewing environment discovery code for conda, poetry, pipenv, pyenv, or venv. |
| argument-hint | manager name (e.g., poetry, conda, pyenv) |
| user-invocable | false |
Environment Manager Discovery Patterns
This skill documents manager-specific discovery patterns, environment variable precedence, and known issues.
Manager Quick Reference
| 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 |
Poetry
Discovery Locations
Virtualenvs cache (default):
- Windows:
%LOCALAPPDATA%\pypoetry\Cache\virtualenvs
- macOS:
~/Library/Caches/pypoetry/virtualenvs
- Linux:
~/.cache/pypoetry/virtualenvs
In-project (when enabled):
Config Precedence (highest to lowest)
- Local config:
poetry.toml in project root
- Environment variables:
POETRY_VIRTUALENVS_*
- Global config:
~/.config/pypoetry/config.toml
Known Issues
| 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 |
Code Pattern
async function getPoetryVirtualenvsPath(): Promise<string> {
const envVar = process.env.POETRY_VIRTUALENVS_PATH;
if (envVar) return envVar;
const localConfig = await readPoetryToml(projectRoot);
if (localConfig?.virtualenvs?.path) {
return resolvePoetryPath(localConfig.virtualenvs.path);
}
return getDefaultPoetryCache();
}
function resolvePoetryPath(configPath: string): string {
if (configPath.includes('{cache-dir}')) {
const cacheDir = getDefaultPoetryCache();
return configPath.replace('{cache-dir}', cacheDir);
}
return configPath;
}
Pipenv
Discovery Locations
Default:
- Linux:
~/.local/share/virtualenvs/ (XDG_DATA_HOME)
- macOS:
~/.local/share/virtualenvs/
- Windows:
~\.virtualenvs\
When WORKON_HOME is set:
- Use
$WORKON_HOME/ directly
Environment Variables
| Var | Purpose |
|---|
WORKON_HOME | Override virtualenv location |
XDG_DATA_HOME | Base for Linux default |
PIPENV_VENV_IN_PROJECT | Create .venv/ in project |
Known Issues
| 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 |
Code Pattern
function getPipenvVirtualenvsPath(): string {
if (process.env.WORKON_HOME) {
return process.env.WORKON_HOME;
}
if (process.platform === 'linux') {
const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
return path.join(xdgData, 'virtualenvs');
}
return path.join(os.homedir(), '.virtualenvs');
}
PyEnv
Discovery Locations
Unix:
~/.pyenv/versions/ (default)
$PYENV_ROOT/versions/ (if PYENV_ROOT set)
Windows (pyenv-win):
%USERPROFILE%\.pyenv\pyenv-win\versions\
- Different directory structure than Unix!
Key Differences: Unix vs Windows
| Aspect | Unix | Windows (pyenv-win) |
|---|
| Command | pyenv | pyenv.bat |
| Root | ~/.pyenv/ | %USERPROFILE%\.pyenv\pyenv-win\ |
| Shims | ~/.pyenv/shims/ | %USERPROFILE%\.pyenv\pyenv-win\shims\ |
Known Issues
| 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 |
Code Pattern
function getPyenvRoot(): string {
if (process.env.PYENV_ROOT) {
return process.env.PYENV_ROOT;
}
if (process.platform === 'win32') {
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');
}
function comparePyenvPaths(pathA: string, pathB: string): boolean {
return path.resolve(pathA) === path.resolve(pathB);
}
Conda
Discovery Locations
Environment locations:
- Base install
envs/ directory
~/.conda/envs/
- Paths in
~/.condarc envs_dirs
Windows Registry:
HKCU\Software\Python\ContinuumAnalytics\
HKLM\SOFTWARE\Python\ContinuumAnalytics\
Shell Activation
| Shell | Activation Command |
|---|
| bash, zsh | source activate envname |
| fish | conda activate envname (NOT source!) |
| PowerShell | conda activate envname |
| cmd | activate.bat envname |
Known Issues
| 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 |
Code Pattern
function getCondaActivationCommand(shell: ShellType, envName: string): string {
switch (shell) {
case 'fish':
return `conda activate ${envName}`;
case 'cmd':
return `activate.bat ${envName}`;
case 'powershell':
return `conda activate ${envName}`;
default:
return `source activate ${envName}`;
}
}
venv
Discovery
Identification:
- Look for
pyvenv.cfg file in directory
- Contains
home and optionally version keys
Version Extraction Priority
version field in pyvenv.cfg
- Parse from
home path (e.g., Python311)
- Spawn Python executable (last resort)
Code Pattern
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;
}
}
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 {
}
return undefined;
}
PET Server (Native Finder)
JSON-RPC Communication
The PET server is a Rust-based locator that communicates via JSON-RPC over stdio.
Known Issues
| 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 |
Code Pattern
async function fetchFromPET<T>(method: string, params: unknown): Promise<T> {
const timeout = 30000;
const result = await Promise.race([
this.client.request(method, params),
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('PET server timeout')), timeout)),
]);
if (!isValidResponse<T>(result)) {
throw new Error(`Invalid response from PET: ${JSON.stringify(result)}`);
}
return result;
}