Skip to content

Refactor environment change events to make getEnvironment a pure read and add refreshEnvironment event#1495

Draft
eleanorjboyd wants to merge 3 commits intomicrosoft:mainfrom
eleanorjboyd:fix/1492-getEnvironment-pure-read
Draft

Refactor environment change events to make getEnvironment a pure read and add refreshEnvironment event#1495
eleanorjboyd wants to merge 3 commits intomicrosoft:mainfrom
eleanorjboyd:fix/1492-getEnvironment-pure-read

Conversation

@eleanorjboyd
Copy link
Copy Markdown
Member

@eleanorjboyd eleanorjboyd commented Apr 29, 2026

fixes #1492

eleanorjboyd and others added 2 commits April 28, 2026 20:12
… and add refreshEnvironment event

Co-authored-by: Copilot <copilot@github.com>
@eleanorjboyd eleanorjboyd requested a review from Copilot April 29, 2026 03:27
@eleanorjboyd eleanorjboyd changed the title Fix/1492 getEnvironment pure read Refactor environment change events to make getEnvironment a pure read and add refreshEnvironment event Apr 29, 2026
@eleanorjboyd eleanorjboyd self-assigned this Apr 29, 2026
@eleanorjboyd eleanorjboyd added the bug Issue identified by VS Code Team member as probable bug label Apr 29, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #1492 where a read-only getEnvironment() call (triggered by project-structure telemetry) could inadvertently mutate the selected interpreter and emit change events, causing the workspace interpreter to “flip” to a different system Python.

Changes:

  • Refactors PythonEnvironmentManagers.getEnvironment() to be a pure read (no cache mutation / no change events) and introduces refreshEnvironment() for explicit cache refresh + event emission.
  • Renames and clarifies environment change events to distinguish “any manager changed” vs “active selection changed”.
  • Adds unit coverage to ensure reads don’t fire events and to validate refreshEnvironment() behavior.
Show a summary per file
File Description
src/features/envManagers.ts Splits read vs mutation paths (getEnvironment vs refreshEnvironment), updates internal cache naming, and renames emitted events.
src/features/pythonApi.ts Switches manager change handling to use refreshEnvironment() so only the selected manager’s changes propagate to the Python API event.
src/internal.api.ts Updates internal EnvironmentManagers interface event names and adds refreshEnvironment() to the contract.
src/extension.ts Updates listeners to the renamed events for view/status refresh and internal logging.
src/features/views/projectView.ts Updates project view refresh trigger to use the renamed “any manager changed” event.
src/test/features/envManagers.unit.test.ts Adds unit tests covering “pure read” semantics and refreshEnvironment() change-event behavior.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment on lines +70 to 75
/** Fires when ANY manager reports a selection change, regardless of whether that manager is selected. */
private _onDidChangeManagerEnvironment = new EventEmitter<DidChangeEnvironmentEventArgs>();

/** Fires when the active (selected) environment for a scope actually changes. */
private _onDidChangeActiveEnvironment = new EventEmitter<DidChangeEnvironmentEventArgs>();
private _onDidChangePackages = new EventEmitter<InternalDidChangePackagesEventArgs>();
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new EventEmitters _onDidChangeManagerEnvironment / _onDidChangeActiveEnvironment are created here, but dispose() only disposes _onDidChangeEnvironmentManager, _onDidChangePackageManager, _onDidChangeEnvironments, and _onDidChangePackages. Please also dispose these new emitters (and any corresponding public events) to avoid leaking listeners when PythonEnvironmentManagers is disposed.

Copilot uses AI. Check for mistakes.
Comment thread src/features/pythonApi.ts
Comment on lines 93 to 100
manager.onDidChangeEnvironment((e) => {
setImmediate(async () => {
// This will ensure that we use the right manager and only trigger the event
// if the user selected manager decided to change the environment.
// This ensures that if a unselected manager changes environment and raises events
// we don't trigger the Python API event which can cause issues with the consumers.
// This will trigger onDidChangeEnvironmentFiltered event in envManagers, which the Python
// API listens to, and re-triggers the onDidChangeEnvironment event.
await this.envManagers.getEnvironment(e.uri);
// Refresh the central cache for this scope. This ensures that only the
// *selected* manager's changes propagate (refreshEnvironment checks
// getEnvironmentManager(scope) internally). It updates the cache and
// fires onDidChangeActiveEnvironment, which the Python API listens to.
await this.envManagers.refreshEnvironment(e.uri);
});
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setImmediate(async () => ...) callback can throw/reject and will become an unhandled promise rejection (nothing awaits it). Wrap the async body in a try/catch (and log via traceError) or use void this.envManagers.refreshEnvironment(e.uri).catch(...) to ensure failures are surfaced without crashing the extension host.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issue identified by VS Code Team member as probable bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project structure telemetry overrides defaultInterpreterPath to latest system python version

2 participants