user opens VS Code python environments extension begins activation
SYNC (activate in extension.ts):
-
create StatusBar, ProjectManager, EnvVarManager, EnvironmentManagers, ManagerReady
-
create TerminalActivation, shell providers, TerminalManager
-
create ProjectCreators
-
setPythonApi()— API object created, deferred resolved (API is now available to consumers) -
create views (EnvManagerView, ProjectView)
-
register all commands
-
activate() returns — extension is "active" from VS Code's perspective
📊 TELEMETRY: EXTENSION.ACTIVATION_DURATION { duration }
ASYNC (setImmediate callback, still in extension.ts):
- spawn PET process (
createNativePythonFinder)- sets up a JSON-RPC connection to it over stdin/stdout
- register all built-in managers in parallel (Promise.all):
- system: create SysPythonManager + VenvManager + PipPackageManager, register immediately (✅ NO PET call, sets up file watcher)
- conda:
getConda(nativeFinder)checks settings → cache → persistent state → PATH - pyenv & pipenv & poetry: create PyEnvManager, register immediately
- ✅ NO PET call — always registers unconditionally (lazy discovery)
- shellStartupVars: initialize
- all managers fire
onDidChangeEnvironmentManager→ ManagerReady resolves
- all registrations complete (Promise.all resolves) — fast, typically milliseconds
--- gate point: applyInitialEnvironmentSelection ---
📊 TELEMETRY: ENV_SELECTION.STARTED { duration, registeredManagerCount, registeredManagerIds, workspaceFolderCount }
Step 1 — pick a manager (resolvePriorityChainCore, per workspace folder + global):
| Priority | Source | Returns |
|---|---|---|
| P1 | pythonProjects[] setting |
manager only |
| P2 | defaultEnvManager setting |
manager only |
| P3 | python.defaultInterpreterPath → nativeFinder.resolve(path) |
manager + environment |
| P4 | auto-discovery: venv → system python fallback | manager only |
Step 2 — get the environment (result.environment ?? await result.manager.get(scope)):
-
If P3 won: environment is already resolved → done, no
get()call needed. -
Otherwise: calls
manager.get(scope), which has two internal paths:Fast path (
tryFastPathGetinfastPath.ts) — entered when_initializedhasn't completed and scope is aUri:- Synchronously create
_initializeddeferred + kick offstartBackgroundInit()(fire-and-forget full PET discovery) - Read persisted env path from workspace state
- If persisted path exists →
resolve(path)→ return immediately (background init continues in parallel) - If no persisted path or resolve fails → fall through to slow path
- On background init failure: clears
_initializedso nextget()retries
Slow path — fast path skipped or failed:
initialize()— lazy, once-only (guarded by_initializeddeferred, concurrent callers await it)nativeFinder.refresh(false)→ PET scan (cached across managers after first call)- Filter results to this manager's type → populate
collection loadEnvMap()→ match persisted paths against discovered envs
- Look up scope in
fsPathToEnv→ return matched env
📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration, scope, prioritySource, managerId, path, hasPersistedSelection }
- Synchronously create
Step 3 — done:
-
env cached in memory (no settings.json write)
-
available via
api.getEnvironment(scope)📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration, result, failureStage?, errorType? }
All three trigger initialize() lazily (once-only, guarded by _initialized deferred). After the first call completes, subsequent calls are no-ops.
manager.get(scope) — environment selection (Step 2 above):
- Called during
applyInitialEnvironmentSelectionor when settings change triggers re-selection - Fast path may resolve immediately; slow path awaits
initialize()
manager.getEnvironments(scope) — sidebar / listing:
- Called when user expands a manager node in the Python environments panel
- Also called by any API consumer requesting the full environment list
- If PET cache populated from earlier
get()→ instant hit; otherwise warm PET call
manager.resolve(context) — path resolution:
- Called when resolving a specific Python binary path to check if it belongs to this manager
- Used by
tryResolveInterpreterPath()in the priority chain (P3) and by external API consumers - Awaits
initialize(), then delegates to manager-specific resolve (e.g.,resolvePipenvPath)
POST-INIT:
- register terminal package watcher
- register settings change listener (
registerInterpreterSettingsChangeListener) — re-runs priority chain if settings change - initialize terminal manager
- send telemetry (manager selection, project structure, discovery summary)