diff --git a/routes/shell_routes.py b/routes/shell_routes.py index 82826f2f01..a51da3b21a 100644 --- a/routes/shell_routes.py +++ b/routes/shell_routes.py @@ -1063,8 +1063,19 @@ async def list_packages( importlib.invalidate_caches() try: user_site = site.getusersitepackages() - if user_site and os.path.isdir(user_site) and user_site not in sys.path: - sys.path.append(user_site) + if user_site and os.path.isdir(user_site): + # Use addsitedir(), NOT a bare sys.path.append(). When a package + # is `pip install --user`'d at runtime (Cookbook → Install) the + # long-lived server process started before the user-site existed, + # so site never processed it — including its `.pth` hooks. On + # Python 3.12+ `distutils` is gone from stdlib and is only + # restored by setuptools' `distutils-precedence.pth`, which ships + # in user-site. basicsr (a realesrgan dep) does `import distutils` + # at import time, so a plain append left the package importable + # but `import distutils` failing → realesrgan probed as + # not-installed until a full process restart. addsitedir() replays + # the `.pth` files so the shim is active. + site.addsitedir(user_site) except Exception: pass if ssh_port and str(ssh_port).strip() not in ("", "22"): diff --git a/tests/test_cookbook_dependency_completion_regression.py b/tests/test_cookbook_dependency_completion_regression.py index bc8c3ada6c..8bca23b90f 100644 --- a/tests/test_cookbook_dependency_completion_regression.py +++ b/tests/test_cookbook_dependency_completion_regression.py @@ -106,4 +106,9 @@ def test_local_dependency_probe_refreshes_user_site_visibility(): assert "importlib.invalidate_caches()" in source assert "user_site = site.getusersitepackages()" in source - assert "if user_site and os.path.isdir(user_site) and user_site not in sys.path:" in source + # addsitedir (not a bare sys.path.append) so user-site `.pth` hooks are + # replayed when a package is installed into an already-running process — + # otherwise setuptools' distutils shim never activates and basicsr-based + # deps (realesrgan) probe as not-installed until a restart. See #4810. + assert "if user_site and os.path.isdir(user_site):" in source + assert "site.addsitedir(user_site)" in source