user opens VS Code python environments extension begins activation
SYNC (activate in extension.ts):
-
create core objects: ProjectManager, EnvironmentManagers, ManagerReady
-
setPythonApi()— API object created, deferred resolved (API is now available to consumers) -
create views (EnvManagerView, ProjectView), status bar, terminal manager
-
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):
- for each manager (system, conda, pyenv, pipenv, poetry):
- check if tool exists (e.g.
getConda(nativeFinder)asks PET for the conda binary) - if tool not found → log, return early (manager not registered)
- if tool found → create manager, call
api.registerEnvironmentManager(manager)- this adds it to the
EnvironmentManagersmap - fires
onDidChangeEnvironmentManager→ManagerReadydeferred resolves for this manager
- this adds it to the
- check if tool exists (e.g.
- for each manager (system, conda, pyenv, pipenv, poetry):
- all registrations complete (Promise.all resolves)
--- gate point: applyInitialEnvironmentSelection ---
📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount }
-
for each workspace folder + global scope (no workspace case), run
resolvePriorityChainCoreto find manager:- P1: pythonProjects[] setting → specific manager for this project
- P2: user-configured defaultEnvManager setting
- P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path)
- P4: auto-discovery → try venv manager (local .venv), fall back to system python
- for workspace scope: ask venv manager if there's a local env (.venv/venv in the folder)
- if found → use venv manager with that env
- if not found → fall back to system python manager
- for global scope: use system python manager directly
- for workspace scope: ask venv manager if there's a local env (.venv/venv in the folder)
-
get the environment from the winning priority level:
--- fork point:
result.environment ?? await result.manager.get(folder.uri)--- left side truthy = envPreResolved | left side undefined = managerDiscoveryenvPreResolved — P3 won (interpreter → manager):
resolvePriorityChainCorecallstryResolveInterpreterPath(): 1.nativeFinder.resolve(path)— single PET call, resolves just this one binary 2. find which manager owns the resolved env (by managerId) 3. return { manager, environment } — BOTH are known → result.environment is set → the??short-circuits → nomanager.get()called, noinitialize(), no full discoverymanagerDiscovery — P1, P2, or P4 won (manager → interpreter):
resolvePriorityChainCorereturns { manager, environment: undefined } → result.environment is undefined → falls through toawait result.manager.get(scope)manager.get(scope)(e.g.CondaEnvManager.get()): 4.initialize()— lazy, once-only per manager (guarded by deferred) a.nativeFinder.refresh(hardRefresh=false): →handleSoftRefresh()checks in-memory cache (Map) for key 'all' (bc one big scan, shared cache, all managers benefit) - on reload: cache is empty (Map was destroyed) → cache miss - falls through tohandleHardRefresh()→handleHardRefresh(): - adds request to WorkerPool queue (concurrency 1, so serialized) - when its turn comes, callsdoRefresh(): 1.configure()— JSON-RPC to PET with search paths, conda/poetry/pipenv paths, cache dir 2.refresh— JSON-RPC to PET, PET scans filesystem - PET may use its own on-disk cache (cacheDirectory) to speed this up - PET streams back results as 'environment' and 'manager' notifications - envs missing version/prefix get an inline resolve() call 3. returns NativeInfo[] (all envs of all types) - result stored in in-memory cache under key 'all' → subsequent managers calling nativeFinder.refresh(false) get cache hit → instant b. filter results to this manager's env type (e.g. conda filters to kind=conda) c. convert NativeEnvInfo → PythonEnvironment objects → populate collection d.loadEnvMap()— reads persisted env path from workspace state → matches path against freshly discovered collection viafindEnvironmentByPath()→ populatesfsPathToEnvmap 5. look up scope infsPathToEnv→ return the matched env📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection }
-
env is cached in memory (no settings.json write)
-
Python extension / status bar can now get the selected env via
api.getEnvironment(scope)📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration (activation→here), result, failureStage?, errorType? }
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)