Python: Fix venv isolation from user-site and base-install site-packages [STUD-81085] - #596
Python: Fix venv isolation from user-site and base-install site-packages [STUD-81085]#596viogroza wants to merge 4 commits into
Conversation
…ges [STUD-81085] Python Scope's embedded interpreter never goes through CPython's own venv activation path (site.py's venv() function, which only triggers for a normally-launched <venv>/Scripts/python.exe), so pointing Path at a venv left both the PEP-370 user-site directory and the base install's own site-packages on sys.path unfiltered. A native package (e.g. pywin32) installed with a different build in either location could resolve its Python module from one and its native DLL dependency from the other, mismatched, copy, surfacing as "DLL load failed while importing win32api: The specified procedure could not be found." Fixes user-site and base-install leakage via PythonEngine.SetNoSiteFlag() (gated on whether the venv requests --system-site-packages), with Controller.ClearUserSiteEnvironmentOverride guarding against an ambient PYTHONNOUSERSITE leaking in from the host's own environment. Also fixes PythonHome being pointed at the venv root instead of its declared base install (breaks stdlib resolution entirely), tightens venv detection against false positives, and adds a version cross-check between a venv and the Python library actually being loaded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the Python activity pack’s handling of virtual environments (venvs) when using an embedded interpreter, ensuring sys.path is correctly isolated from PEP-370 user-site and (when appropriate) the base install’s site-packages, and surfacing clearer errors for misconfigured venv/library pairings.
Changes:
- Adds shared venv detection (
pyvenv.cfg) and uses it to drive runtime initialization behavior. - Updates engine initialization to set an appropriate
PythonHome, gatePy_NoSiteFlag/SetNoSiteFlag()based on venv flags, and preservesitecustomize.pyexecution. - Adds validation and regression tests for venv detection, version mismatch, user-site isolation, base-site leakage, and ambient
PYTHONNOUSERSITEbehavior.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Activities/Python/UiPath.Python/Properties/UiPath.Python.resx | Adds localized error text for venv/library version mismatch. |
| Activities/Python/UiPath.Python/Properties/UiPath.Python.Designer.cs | Adds generated accessor for the new localized resource. |
| Activities/Python/UiPath.Python/Properties/AssemblyInfo.cs | Grants internals visibility to UiPath.Python.Tests for new regression coverage. |
| Activities/Python/UiPath.Python/Impl/VenvDetection.cs | Introduces shared, bounded venv detection + config parsing (pyvenv.cfg). |
| Activities/Python/UiPath.Python/Impl/OutOfProcessEngine.cs | Plumbs venv flag into host spawning to clear ambient PYTHONNOUSERSITE when needed. |
| Activities/Python/UiPath.Python/Impl/Engine.cs | Fixes PythonHome resolution for venvs, applies SetNoSiteFlag() for default venvs, and preserves sitecustomize.py. |
| Activities/Python/UiPath.Python/EngineProvider.cs | Adds venv-vs-library version cross-check during installation validation. |
| Activities/Python/UiPath.Python.Tests/VenvVersionValidationTests.cs | Regression tests for upfront venv/library version mismatch handling. |
| Activities/Python/UiPath.Python.Tests/VenvUserSiteIsolationTests.cs | Regression coverage for user-site suppression, base-site leakage, sitecustomize preservation, and ambient env-var behavior. |
| Activities/Python/UiPath.Python.Tests/VenvDetectionTests.cs | Fast unit tests for venv detection shapes and false-positive avoidance. |
| Activities/Python/Shared/UiPath.Shared.Service/Client/Controller.cs | Adds child-process environment scrubbing for PYTHONNOUSERSITE when requested. |
Files not reviewed (1)
- Activities/Python/UiPath.Python/Properties/UiPath.Python.Designer.cs: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
alexandru-petre
left a comment
There was a problem hiding this comment.
Code review — findings
Verdict: NEEDS CHANGES (1 HIGH, 8 MEDIUM, 2 LOW). Reviewed all 11 changed files against STUD=81085 (description + all 7 comments) and the PR description. 12 inline findings below.
Highlights
- HIGH — the
PythonHomefix does not cover Microsoft Store Python venvs.pyvenv.cfg'shomethere points at the app-execution-alias folder, which contains only reparse-point.exestubs — noLib, noLib\encodings. Verified locally against a stdlib-venvconfig withhome = ...\WindowsApps\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0. Not a regression (the oldPythonHome = _pathwas equally broken), but the headline fix does not close a mainstream Windows install shape. - The new venv version cross-check silently no-ops for
virtualenv/uvvenvs, which writeversion_inforather thanversion. ClearUserSiteEnvironmentOverridecontradicts its own native-parity rationale — a natively-launched interpreter does honourPYTHONNOUSERSITE=1.
On the failed quality gate
Pulled the 34 issues from the SonarCloud API for this PR. D Security and D Reliability are driven entirely by pre-existing code in the touched files, not by this diff: S4790 (MD5 in Engine.Hash) and S6444 (regex without timeout in EngineProvider) for Security; S2551 (lock(this) x3) and S2445 for Reliability. The only genuinely PR-caused Sonar issue is S3776 on Engine.Initialize (complexity 29 vs. limit 15), commented inline. Suggest a separate PR or a gate exception rather than scope creep here.
Evidence gap
The ticket's reproduction package (Test-Pywin32VenvRepro (2).zip, attached 2026-08-20) postdates this branch's only commit (3b2a9d61, 2026-08-19). Nothing in the PR records a run against it — see the inline comment on VenvUserSiteIsolationTests.cs.
Done well
- The
SetNoSiteFlagblock documents why the env-var approach was abandoned and why call ordering matters — exactly the reasoning that otherwise gets re-litigated six months later. - Replacing the unbounded
GetVenvPath(maxLevels: 3)ancestor walk with root-or-Scripts/bindetection is the right narrowing, and both false-positive shapes are pinned by tests. - Both sides of the
--system-site-packagesfork assert opposite outcomes from the same code path — the pairing that actually proves the flag is load-bearing. - Catching the second-order
sitecustomize.pyloss and pinning it withVenv_SiteCustomize_Still_Runswas sharp. - Clean migration hygiene: zero surviving references to the removed
IsVenv/GetVenvPath. Localization convention followed — only the canonical English resx was touched.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- Activities/Python/UiPath.Python/Properties/UiPath.Python.Designer.cs: Generated file
Suppressed comments (1)
Activities/Python/UiPath.Python/Impl/VenvDetection.cs:81
- A
Pathending in a separator breaks launcher-folder detection.folderNameis computed from the trimmed value, butPath.GetDirectoryName(path)is not; for.../venv/bin/it returns.../venv/bin, sopyvenv.cfgis searched in the launcher folder and the venv isolation/PythonHome fix is skipped. Derive the parent from the same trimmed path (and add a trailing-separator case to the detection tests).
return TryReadVenvConfig(Path.GetDirectoryName(path));
Fixes findings from PR #596 review: - PythonHome falls back to LibraryPath's directory when the venv's declared home (pyvenv.cfg) doesn't actually carry a stdlib (e.g. a Microsoft Store Python's app-execution-alias folder). - VenvDetection.GetVenvInfo no longer misses the venv root for a Path ending in a separator (Path.GetDirectoryName on a separator-terminated path only strips the separator, it doesn't walk up). - An unreadable pyvenv.cfg (locked ACLs, AV, concurrent pip rewrite) now returns null instead of throwing out of the public ValidateInstallation. - pyvenv.cfg's "version_info" (virtualenv/uv) is read as a fallback for "version" (stdlib venv), so the venv/library version cross-check isn't silently skipped for venvs created by those tools. - sitecustomize.py/usercustomize.py precedence fixed for --system-site-packages venvs: the venv's own copy now actually gets a chance to run instead of being permanently shadowed by whatever site.main() already cached before the venv's site-packages was added. - Removed Controller.ClearUserSiteEnvironmentOverride entirely: an ambient PYTHONNOUSERSITE is now honored for --system-site-packages venvs exactly like a natively-activated interpreter would, instead of being silently stripped. - Extracted Engine.ConfigureRuntime out of Initialize (addresses the Sonar S3776 cognitive-complexity finding, the only genuinely PR-caused issue behind the failing quality gate — the rest is pre-existing debt in touched files, confirmed via diff, left alone). - Made PostInitializationVenvSetup static and added GC.SuppressFinalize to three tests' Dispose() (CA1816/CA1822/S2325 — all genuinely new, cheap, zero-behavior-change). - Added regression tests for all of the above, plus direct tests for the Windows "Lib" vs POSIX "lib" case-sensitive path segments, each verified to actually fail without its corresponding fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alexandru-petre caught (PR #596, discussion_r3854255951) that the previous fix — unconditionally popping "sitecustomize" from sys.modules and re-importing for --system-site-packages venvs — was worse than the bug it fixed: when the venv has no sitecustomize.py of its own, the re-import still resolves to the same base/user copy, running its side effects a second time. Gate the pop+reimport on the venv's own site-packages actually containing a sitecustomize.py/usercustomize.py. When it does, the venv's copy (now first on sys.path) wins as intended. When it doesn't, the module site.main() already cached — already the correct, highest-priority one — is left untouched, so it runs exactly once. Added Venv_With_SystemSitePackages_And_No_Own_SiteCustomize_Does_Not_Rerun_UserSite_Copy, verified to fail (2 executions instead of 1) against the previous, unconditional version of the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review comments addressedTwo follow-up commits: Fixed:
Not fixed, called out explicitly rather than silently skipped:
Every fix in both commits was verified the same way: temporarily revert it, confirm the corresponding test fails, restore it, confirm green. Full suite: 98/98 passing. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- Activities/Python/UiPath.Python/Properties/UiPath.Python.Designer.cs: Generated file
Suppressed comments (3)
Activities/Python/UiPath.Python/Impl/Engine.cs:170
SetNoSiteFlag()suppresses all ofsite.main(), not only user/base site-package discovery. The manual post-initialization path restores.pthprocessing and customization imports, but notsite.main()'s other observable setup (quit/exit,help, copyright helpers, and readline completion). These built-ins therefore disappear for default venv scopes even though native venv startup and the previous implementation provide them. Restore the non-path portions of site initialization explicitly.
if (venv != null && venv.ShouldDisableUserSite)
PythonEngine.SetNoSiteFlag();
Activities/Python/UiPath.Python/Impl/Engine.cs:423
- Assigning
sys.prefixdoes not updatesite.PREFIXES, which was captured whensitewas imported whilePythonHomestill pointed at the base install. Consequentlysite.getsitepackages()continues to report base-install locations and omits the venv (including for default venvs), diverging from native activation and potentially directing package tooling to the wrong environment. Synchronizesite.PREFIXESwith the venv and, only for--system-site-packages, the base prefixes.
sys.prefix = venv.Root;
sys.exec_prefix = venv.Root;
Activities/Python/UiPath.Python/Impl/Engine.cs:461
- This guard does not actually mirror
site.main()'sENABLE_USER_SITEcheck. In a--system-site-packagesvenv with ambientPYTHONNOUSERSITE=1, initialization setssite.ENABLE_USER_SITEto false, but a venv-localusercustomize.pystill satisfies this condition and is executed explicitly. Check the runtimesite.ENABLE_USER_SITEvalue before callingexecusercustomize()so the ambient suppression applies to customization as well as user-site.pthfiles.
// execusercustomize() mirrors site.main()'s own "if ENABLE_USER_SITE:" guard —
// only called when user-site isn't suppressed, consistent with not calling it at
// all for the default (user-site-disabled) case. Same re-import gating as above.
if (!venv.ShouldDisableUserSite && File.Exists(Path.Combine(sitePackagesPath, "usercustomize.py")))
alexandru-petre
left a comment
There was a problem hiding this comment.
Review of 359f5fc3 + 49dde878
Thanks — every finding from the first round is either fixed or consciously deferred with a stated reason, and the deferrals are the right calls. Re-reviewed the two new commits and also validated Copilot's latest review, including its three suppressed comments, since those are collapsed and easy to miss.
Verified correct
Controller.csreverted to the exact pre-PR blob (a1a7d1a9→e7510269) — no residue.OutOfProcessEnginealso drops its now-redundantGetVenvInfocall, removing a duplicatepyvenv.cfgread in the parent.- The
HasStdlibfallback goes further than asked, in a good way: since both call sites passVersion.Auto,_pathalways contains a Python exe, so the only non-venv case where the fallback fires is one that was already broken (e.g.Pathpointed at a Store alias folder). Strict improvement on Windows. - The
try/catchwraps theforeach, not just theFile.ReadLines()call — correct placement for a lazy enumerator, and the usual place people get it wrong. - Tests are strong:
UnreadablePyvenvCfg_...takes a realFileShare.Nonelock rather than mocking;VersionInfoOnly_CrossChecksJustLikeVersionasserts the throw, proving the cross-check engages rather than merely parsing;Venv_With_SystemSitePackages_And_No_Own_SiteCustomize_Does_Not_Rerun_UserSite_CopyusesAssert.Singleto pin "exactly once", which is precisely the double-execution regression. The PYTHONNOUSERSITE test was correctly inverted and renamed to..._Honors_.... TheWindowsStdlibLandmark/PosixStdlibDirectorycasing tests, justified by NTFS case-folding on Windows-only CI, are a genuinely thoughtful bit of design.
New findings (4 inline below)
All four live in the 49dde878 sitecustomize work or in what SetNoSiteFlag silently removes. Two are mine, two are Copilot suppressed comments I verified and am surfacing.
Copilot's latest review — validated
| Copilot finding | Verdict |
|---|---|
POSIX Path.GetDirectoryName(_libraryPath) is not a valid prefix (Engine.cs:164) |
Valid — endorsed in-thread with a minimal fix |
Tests inherit ambient PYTHONNOUSERSITE (VenvUserSiteIsolationTests.cs:45) |
Valid — endorsed in-thread; now load-bearing since the fix deliberately honours the ambient value |
Suppressed: SetNoSiteFlag also drops quit/exit/help builtins (Engine.cs:170) |
Valid — surfaced below, the strongest of the three |
Suppressed: sys.prefix assignment leaves site.PREFIXES stale (Engine.cs:423) |
Valid — surfaced below as LOW |
Suppressed: usercustomize guard doesn't mirror ENABLE_USER_SITE (Engine.cs:461) |
Valid — merged into my finding on the same block |
One recurring defect in Copilot's output worth knowing about: its "This issue also appears in the following locations" lists are unreliable. On Engine.cs:164 it points at lines 169, 422 and 458 — which are its own three suppressed findings, not other instances of the POSIX problem. It did the same thing last round. Read those cross-references with suspicion.
|
|
||
| sys.prefix = venvPath; | ||
| sys.exec_prefix = venvPath; | ||
| sys.prefix = venv.Root; |
There was a problem hiding this comment.
[Correctness/LOW] Assigning sys.prefix leaves site.PREFIXES pointing at the base install
Also from Copilot's suppressed section (Engine.cs:423); verified and agreed, at low severity.
site.PREFIXES is initialised at module-import time from [sys.prefix, sys.exec_prefix]. Py.Import("site") on the line above runs while PythonHome still points at the base install, and the sys.prefix assignment here does not retroactively update it. So site.getsitepackages() keeps reporting base-install locations and omits the venv — for default venvs too, not just --system-site-packages.
This does not affect script execution or the STUD=81085 fix, which is why LOW: it is a fidelity gap that shows up only for code introspecting the environment (packaging helpers, pip-adjacent tooling, diagnostics that print getsitepackages()).
Suggested fix — one line, right after the sys.exec_prefix assignment:
// site.PREFIXES was captured at import time, while PythonHome still pointed at the base
// install; resync it so site.getsitepackages() reports the venv like native activation does.
site.PREFIXES = venv.IncludeSystemSitePackages
? new[] { venv.Root, basePrefix }
: new[] { venv.Root };Reasonable to defer to a follow-up if you would rather keep this patch tight — but it is cheap, and it is the kind of thing that is much harder to justify revisiting later.
There was a problem hiding this comment.
Deferring this one rather than folding it in now, per your own "reasonable to defer to a follow-up" note. Implementing it cleanly needs the resolved base prefix threaded into PostInitializationVenvSetup (it currently only receives venv), which is a bigger plumbing change than the snippet implies for a LOW-severity, introspection-only gap — doesn't affect script execution or the STUD-81085 fix itself. Given how much ground the last two rounds already covered (including two rounds of my own fixes needing a fix), I'd rather not rush a fourth change into this same method without it getting the same scrutiny. Happy to open a follow-up ticket for it, or fold it in here if you'd rather have it now — your call.
…nVenvSetup regressions Addresses further PR #596 review findings from Copilot and alexandru-petre: - ResolvePrefixFromLibraryPath now walks up ancestors testing HasStdlib at each level, instead of trusting the library's immediate parent directory as the prefix. On POSIX the shared library sits one or more levels below the real prefix (plain lib/, or a multiarch triplet like lib/x86_64-linux-gnu/), so the previous one-level fallback could set PythonHome to a directory with no stdlib either. - HasStdlib now also recognizes a *._pth file as valid evidence of a home: CPython's ._pth-based isolation (the official Windows embeddable distribution, but not Windows-exclusive) resolves its stdlib from a bundled zip rather than an unpacked Lib folder, which the folder-based checks alone would wrongly treat as "no stdlib". - Fixed a regression in the previous commit's sitecustomize fix: the File.Exists gate meant to stop double-execution for --system-site-packages venvs was also applied to the default-venv case, where SetNoSiteFlag means nothing was ever cached — so a default venv with no venv-local sitecustomize.py silently stopped picking up a base-install Lib\sitecustomize.py (e.g. corporate proxy/logging setup). Split into two branches: default venv calls execsitecustomize() unconditionally again; --system-site-packages keeps the gated pop+reimport. - Removed the usercustomize block entirely: it checked the venv's own site-packages, but usercustomize.py only ever lives in the per-user site directory, so it was dead code — and on the rare path it could fire, its guard (a static config flag) didn't match the runtime ENABLE_USER_SITE value, so it could fire exactly when user-site customization should be suppressed. Restored the original one-line rationale for not calling it at all here. - SetNoSiteFlag also skips site.main()'s setquit()/setcopyright()/ sethelper(), silently dropping the exit/quit/help/copyright/credits/ license builtins from a default-venv scope. Restored them explicitly (not enablerlcompleter(), which only matters interactively). - VenvUserSiteIsolationTests' constructor now clears PYTHONUSERBASE/ PYTHONNOUSERSITE after saving them, so tests don't inherit whatever the host machine's own ambient environment happens to have. Added regression tests for all of the above (._pth recognition, ancestor-walk success/exhaustion, default-venv sitecustomize from a base install, exit/help builtins), each verified to fail without its corresponding fix and pass with it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Second round of review comments addressedCommit Fixed — new findings from this round:
Deferred, with the reviewer's own suggestion to do so:
Every fix verified the same way as before: temporarily revert, confirm the corresponding test fails, restore, confirm green. Full suite: 104/104 passing. 🤖 Generated with Claude Code |
|
| else | ||
| return GetVenvPath(Path.GetDirectoryName(venvPath), maxLevels - 1); | ||
|
|
||
| var dir = Path.GetDirectoryName(libraryPath); |
| // like a natively-activated default venv does. | ||
| site.execsitecustomize(); | ||
| } | ||
| else if (File.Exists(Path.Combine(sitePackagesPath, "sitecustomize.py"))) |
alexandru-petre
left a comment
There was a problem hiding this comment.
Approving ✅
Four rounds, 24 threads, 21 resolved. Every MEDIUM and the one HIGH are fixed and verified against 0a1e7ba1 — not just marked done. What stands out is that the fixes were checked rather than assumed: the merged sitecustomize gate was confirmed by restoring it and watching a new test fail, and the double-execution regression in 49dde878 was caught and corrected in the same session it was introduced.
The ._pth recognition in HasStdlib deserves a specific mention — it was the author's own catch, and it is the sharpest thing in the PR. Without it, commits 2–3 were passing their venv tests through the fallback path, since the embeddable runtime has no unpacked Lib. The primary home-based resolution was silently untested and nobody had flagged it.
Nits — all non-blocking, all one-to-three lines
None of these justify another round on their own; batch them into a follow-up if you prefer.
1. [Correctness/LOW] Stdlib landmark is satisfied by an empty directory — Engine.cs:382, same shape at :401
HasStdlib accepts any folder containing Lib\encodings (POSIX: lib/python*/encodings) regardless of contents, so an empty directory of that name passes and PythonHome gets set to a home that still cannot bootstrap. Venv_SiteCustomize_Still_Runs_From_BaseInstall_When_Venv_Has_None shows how cheap that is to satisfy — it creates fakebase\Lib\encodings empty purely to get past the check. Testing for Lib\encodings\__init__.py instead would tighten a check the whole fallback now hinges on.
2. [Bugs/LOW] The ancestor walk aborts on one unreadable ancestor — Engine.cs:378, reached from :417
ResolvePrefixFromLibraryPath calls HasStdlib at each level, and HasStdlib now does Directory.EnumerateFiles(pythonHome, "*._pth"), which throws UnauthorizedAccessException on a directory the robot account cannot enumerate. One denied ancestor aborts the whole walk instead of continuing upward, failing init even when a higher ancestor would have resolved. Before 0a1e7ba1 the fallback did no I/O at all. A try/catch { continue; } around the per-level probe matches what TryReadVenvConfig and the EngineProvider validators already do.
3–4. The two open Copilot findings — both valid, both LOW, both still unaddressed:
- Bare relative
LibraryPath(Engine.cs:422). One correction to that comment:Path.GetDirectoryName("python314.dll")returns"", notnull— but the effect is exactly as described, thewhileguard fails immediately and the fallback silently no-ops in the case it exists to repair.Path.GetFullPathfirst. sitecustomizeas a package or bytecode module (Engine.cs:498). Narrow — needs--system-site-packagesand a non-source form and a cached base/user copy.
Two deferrals that need a home after merge
Both were deferred for good reasons, and I agree with both calls — but once this merges, the threads carrying them stop being somewhere anyone looks:
site.PREFIXESleft stale — fidelity only, no execution impact; needs the resolved base prefix threaded through to fix cleanly.- No regression coverage against an installer-based Python. This is the one I would still like closed, and it is the reason I am calling it out here rather than letting it disappear. The suite runs entirely against the embeddable distribution, whose
._pthbootstrap resolves the stdlib independently ofPythonHome— structurally the opposite of the reported configuration (3.10, installer-based, real pywin32). Declining to claim coverage you cannot run was the right instinct. The ticket's ownTest-Pywin32VenvRepro (2).zipexists; one manual run recorded in the PR body would close it, and for a customer-facing patch that is worth the twenty minutes.
Suggest a follow-up ticket carrying both, linked from this PR.
On the quality gate
Still red, still not this PR's doing. Verified against the SonarCloud API at this head: all seven issues driving the D Security / D Reliability ratings are pre-existing code in touched files — S4790 (MD5 in Engine.Hash), S6444 (regex without timeout, ×2), S2551 (lock(this), ×3), S2445. The one genuinely PR-caused issue, S3776 on Engine.Initialize, is CLOSED / FIXED after the ConfigureRuntime extraction. Merge will need either a separate cleanup PR or a gate exception — please do not absorb those into this one.




Summary
site.py'svenv()function only triggers for a normally-launched<venv>/Scripts/python.exe), so pointingPathat a venv left both the PEP-370 user-site directory and the base install's own site-packages onsys.pathunfiltered.DLL load failed while importing win32api: The specified procedure could not be found.PythonEngine.SetNoSiteFlag()(gated on whether the venv requests--system-site-packages). For a--system-site-packagesvenv, an ambientPYTHONNOUSERSITEin the host's own environment is now honored exactly as a natively-activated interpreter would (no override).PythonHomebeing pointed at the venv root instead of its declared base install (breaks stdlib resolution entirely) — falling back toLibraryPath's own directory when the declared base install doesn't actually carry a stdlib (e.g. a Microsoft Store Python) — tightens venv detection against false positives and a trailing-separator miss, and adds a version cross-check between a venv and the Python library actually being loaded.sitecustomize.py/usercustomize.pyprecedence for--system-site-packagesvenvs so the venv's own copy gets a chance to run, without re-running a base/user copy's side effects a second time when the venv has none of its own.Behaviour changes
EngineProvider.ValidateInstallation(called fromPythonScope.ExecuteAsync/PythonService) now throwsNotSupportedExceptionwhen a venv's declared Python version (pyvenv.cfg) doesn't match the version actually loaded fromLibraryPath. Previously this pairing would proceed and fail later with a confusing, version-specific stdlib error.Test plan
UiPath.Python.Tests,UiPath.Python.Activities.Tests,UiPath.Python.Activities.API.Testsall pass (98 total)--system-site-packages), base-install site-packages leak,sitecustomize.py/usercustomize.pypreservation and precedence (including the no-double-execution case),PythonHomeresolution and its Microsoft-Store/no-stdlib fallback, venv detection (root/Scripts/bin/trailing-separator/false-positive/unreadable-file), venv/library version mismatch (includingversion_info-only configs), ambientPYTHONNOUSERSITEhandling, and the WindowsLibvs POSIXlibcase-sensitive path segments🤖 Generated with Claude Code