Repository navigation
Commit bb6e0f4
Fix false-positive "environment manager not registered" warning on startup (#1533)
### Summary
Fixes a startup race that produces a spurious warning popup:
> Default environment manager `pypa.hatch:hatch` could not be applied:
Environment manager `pypa.hatch:hatch` is not registered
Even though the manager is actually registered and working correctly
seconds later. The same race affects any third-party environment-manager
extension (e.g. `PyPA.hatch`, `pixi-code`).
Fixes #1517. The same root cause likely produces an equivalent symptom
in other contributing extensions (e.g. the `pixi-code` extension, where
a similar warning has been reported); this fix is generic and applies to
any third-party manager that registers via the public API.
---
### The bug, in plain language
If a user sets `python-envs.defaultEnvManager` to a manager contributed
by a separate extension (Hatch, Pixi, etc.), they sometimes see a
warning popup at startup claiming the manager is not registered — even
though the manager *is* registered and works fine. The popup is a false
positive.
It does not reproduce on every machine. Some users hit it every launch,
some never; on at least one user's machine, uninstalling system-level
`conda` + `poetry` made the popup stop appearing (see issue thread).
---
### Root cause: a race between two concurrent `activate()` timelines
Once `vscode-python-environments` returns its API (`extension.ts` line
712), VS Code starts activating any dependent extensions that declare
`"extensionDependencies": ["ms-python.vscode-python-envs"]` (Hatch and
Pixi do exactly this). At that moment two `async` functions are running
concurrently on the same Node.js event loop in the extension host:
**Timeline A** — our `setImmediate(async () => { … })` block in
`extension.ts` (line 568+):
```ts
await createNativePythonFinder(...); // spawns pet binary
await Promise.all([
safeRegister('system', ...),
safeRegister('conda', ...),
safeRegister('pyenv', ...),
safeRegister('pipenv', ...),
safeRegister('poetry', ...),
safeRegister('shellStartupVars', ...),
]);
await applyInitialEnvironmentSelection(...); // <-- consults the registry
```
**Timeline B** — `PyPA.hatch.activate()` (or any other contributing
extension):
```ts
const api = await getPythonApi();
// ...possibly runs `hatch --version` or other async setup...
api.registerEnvironmentManager(hatchManager); // <-- writes to the registry
```
Both timelines yield at every `await`. The race target is the registry
Map (`src/features/envManagers.ts` line 125,
`this._environmentManagers.set(managerId, mgr)` — synchronous, the
source of truth).
`applyInitialEnvironmentSelection` → `resolvePriorityChainCore`
(`src/features/interpreterSelection.ts` lines 99–114) calls
`envManagers.getEnvironmentManager('pypa.hatch:hatch')`. If Timeline B
hasn't yet reached `registerEnvironmentManager` when Timeline A executes
that lookup, an error is recorded with kind `managerNotRegistered`.
Several more `await`s then run; by the time `notifyUserOfSettingErrors`
is invoked, the manager often *has* registered — but the warning is
shown anyway because the previously-recorded error is never
re-evaluated.
#### Why timing varies by machine
The race outcome depends on:
- How quickly `pet` (the bundled Rust binary) streams JSON-RPC discovery
messages back to the extension host. Each message is an I/O callback on
the main event loop. More installed Python tooling (conda, poetry) →
more environments to discover → more event-loop traffic → Hatch's `await
child_process` for `hatch --version` resumes later.
- How long the third-party extension's own startup `await`s take.
- General OS-level CPU and disk I/O contention.
Removing conda and poetry from the user's system doesn't "fix" anything
in our code path — it just shifts the timing enough that Timeline B
usually wins.
---
### The fix
Re-check the live manager registry at notification time, and drop
"manager not registered" errors that became stale during the `await`s
between when the error was recorded and when the warning is about to be
shown.
---
### Why not "fix the race itself"?
This is a real race condition, not just a stale-data bug, so the most
architecturally pure fix would be to remove the race entirely — for
example, by deferring `applyInitialEnvironmentSelection` until either
(a) an expected set of third-party manager registrations has been
observed, or (b) some deadline has elapsed.
That kind of fix was considered and rejected for this change because:
1. **The set of "expected" managers is unknown to us.** Third-party
env-manager extensions opt in by calling `registerEnvironmentManager` at
their own pace. We have no manifest of who is going to register, so any
deadline is arbitrary.
2. **Adding deadlines couples activation time to extension-host
scheduling.** A safe deadline on a slow machine could delay the user's
interpreter selection visibly; an aggressive deadline still has the same
race, just on a smaller timescale.
3. **Existing real not-registered errors must still be surfaced
quickly.** If a user genuinely typos a manager id, they want the warning
fast — not after a multi-second wait.
4. **The blast radius would be much larger.** Reordering
activation/selection touches the whole initialization sequence,
including pet startup, terminal management, project resolution, and
telemetry timing — all of which are currently sequenced around the
existing `setImmediate` boundary.
The chosen fix instead changes the *gate* (the notification), not the
*race* (the order of activation). It is essentially: "before yelling at
the user, confirm the problem is still real."
Future work could pursue the architectural fix as a follow-up, but it
should be evaluated independently of #1517.
---
### Tradeoffs and known limits
- If a third-party manager genuinely takes longer to activate than
`applyInitialEnvironmentSelection` (e.g. the manager extension's own
`activate()` is unusually slow), the popup can still fire. After this
fix the user can dismiss it and the manager will continue to work
normally; no permanent state is corrupted.
- The fix only filters errors whose `kind === 'managerNotRegistered'`.
Real not-registered errors (typo'd ids, uninstalled extension, etc.),
unresolved-variable errors, and path-resolution errors still notify as
before.
- This applies to both the awaited workspace-scope notification path and
the deferred background global-scope notification path; both call sites
pass `envManagers` to the updated helper.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 4aaf742 commit bb6e0f4
2 files changed
Lines changed: 146 additions & 16 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
47 | 49 | | |
48 | 50 | | |
49 | | - | |
50 | | - | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
84 | 85 | | |
85 | | - | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| |||
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| 102 | + | |
102 | 103 | | |
103 | | - | |
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
| 121 | + | |
121 | 122 | | |
122 | | - | |
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| 141 | + | |
141 | 142 | | |
142 | | - | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| 158 | + | |
158 | 159 | | |
159 | | - | |
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
| |||
383 | 383 | | |
384 | 384 | | |
385 | 385 | | |
386 | | - | |
387 | | - | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
388 | 389 | | |
389 | 390 | | |
390 | 391 | | |
| |||
397 | 398 | | |
398 | 399 | | |
399 | 400 | | |
400 | | - | |
401 | | - | |
402 | | - | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
403 | 406 | | |
404 | 407 | | |
405 | 408 | | |
| |||
409 | 412 | | |
410 | 413 | | |
411 | 414 | | |
412 | | - | |
| 415 | + | |
413 | 416 | | |
414 | 417 | | |
415 | 418 | | |
| |||
430 | 433 | | |
431 | 434 | | |
432 | 435 | | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
433 | 467 | | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
434 | 472 | | |
435 | 473 | | |
436 | 474 | | |
| |||
442 | 480 | | |
443 | 481 | | |
444 | 482 | | |
| 483 | + | |
445 | 484 | | |
446 | 485 | | |
447 | 486 | | |
| |||
451 | 490 | | |
452 | 491 | | |
453 | 492 | | |
454 | | - | |
| 493 | + | |
455 | 494 | | |
456 | 495 | | |
457 | 496 | | |
458 | 497 | | |
459 | 498 | | |
460 | 499 | | |
461 | 500 | | |
462 | | - | |
| 501 | + | |
463 | 502 | | |
464 | 503 | | |
465 | 504 | | |
466 | 505 | | |
467 | 506 | | |
468 | 507 | | |
469 | 508 | | |
470 | | - | |
| 509 | + | |
471 | 510 | | |
472 | 511 | | |
473 | 512 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
968 | 968 | | |
969 | 969 | | |
970 | 970 | | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
971 | 1062 | | |
972 | 1063 | | |
973 | 1064 | | |
| |||
0 commit comments