Skip to content

Commit 6450988

Browse files
authored
πŸ› fix(pip): set PIP_USER=0 to prevent --user installs in virtualenvs (#3719)
Users with `pip config --user` configured globally experienced installation failures in tox virtualenvs. πŸ› When pip attempted to honor the `--user` flag inside virtualenvs, it crashed because user site-packages aren't visible in isolated environments. This regression was introduced during the tox 4 rewrite. Tox 3 correctly set `PIP_USER=0` to override user configuration, but this protection was lost in the rewrite. The fix restores this behavior by setting the environment variable in all virtualenv-based Python environments. The solution follows pip's documented environment variable precedence where `PIP_USER=0` explicitly disables `--user` installs regardless of config file settings. This preserves user workflows while ensuring tox virtualenvs remain isolated. ✨ Fixes #3010
1 parent f17313e commit 6450988

5 files changed

Lines changed: 25 additions & 2 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Set ``PIP_USER=0`` environment variable when running pip commands in virtualenvs to prevent pip from attempting
2+
``--user`` installs when users have ``pip config --user`` configured globally β€” by :user:`gaborbernat`.

β€Ždocs/explanation.rstβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ External commands need to be explicitly allowed via :ref:`allowlist_externals`.
113113
virtualenv first.
114114
4. **set_env** -- values defined here are applied last and can override anything from the previous steps, including
115115
``PATH``.
116-
5. **Injected variables** -- tox adds ``TOX_ENV_NAME``, ``TOX_WORK_DIR``, ``TOX_ENV_DIR``, ``VIRTUAL_ENV``, and
117-
``PYTHONIOENCODING=utf-8``. These cannot be overridden.
116+
5. **Injected variables** -- tox adds ``TOX_ENV_NAME``, ``TOX_WORK_DIR``, ``TOX_ENV_DIR``, ``VIRTUAL_ENV``,
117+
``PIP_USER=0``, and ``PYTHONIOENCODING=utf-8``. These cannot be overridden.
118118

119119
**PATH behavior**: because tox prepends the virtualenv ``bin/`` directory to ``PATH`` at step 3, commands like
120120
``python`` and ``pip`` resolve to the virtualenv versions. If you override ``PATH`` in ``set_env``, be aware that this

β€Ždocs/reference/config.rstβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,9 @@ always set regardless of the ``pass_env`` or ``set_env`` configuration and canno
819819
- The directory of the current tox environment (e.g. ``.tox/3.12``).
820820
- - ``PYTHONIOENCODING``
821821
- Always set to ``utf-8`` to ensure consistent encoding for standard I/O.
822+
- - ``PIP_USER``
823+
- Always set to ``0`` to prevent pip from attempting ``--user`` installs inside virtualenvs, which would fail
824+
because user site-packages aren't visible. Only set when using ``virtualenv``\-based environments.
822825
- - ``TOX_PACKAGE``
823826
- The path(s) to the built package artifact(s), joined by ``os.pathsep`` if there are multiple. Only set in run
824827
environments where a package has been built.

β€Žsrc/tox/tox_env/python/virtual_env/api.pyβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def runs_on_platform(self) -> str:
176176
def environment_variables(self) -> dict[str, str]:
177177
environment_variables = super().environment_variables
178178
environment_variables["VIRTUAL_ENV"] = str(self.conf["env_dir"])
179+
environment_variables["PIP_USER"] = "0"
179180
return environment_variables
180181

181182
@classmethod

β€Žtests/tox_env/python/virtual_env/test_virtualenv_api.pyβ€Ž

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,20 @@ def test_list_dependencies_command(tox_project: ToxProjectCreator) -> None:
172172
result.assert_success()
173173
request: ExecuteRequest = execute_calls.call_args[0][3]
174174
assert request.cmd == ["python", "-m", "pip", "freeze"]
175+
176+
177+
def test_pip_user_disabled(tox_project: ToxProjectCreator) -> None:
178+
proj = tox_project(
179+
{
180+
"tox.toml": """
181+
[env_run_base]
182+
package = "skip"
183+
commands = [
184+
["python", "-c", "import os; print('PIP_USER=' + os.environ.get('PIP_USER', 'NOT_SET'))"]
185+
]
186+
""",
187+
},
188+
)
189+
result = proj.run("r", "-e", "py")
190+
result.assert_success()
191+
assert "PIP_USER=0" in result.out

0 commit comments

Comments
Β (0)