Skip to content

nox virtualenv backend uses the sys.executable to run virtualenv with the -p flag targeting the interpreter, which leads to incorrect packages installed in the virtual enviroment #1021

@zzzeek

Description

@zzzeek

Current Behavior

the virtualenv backend runs virtualenv with the "-p" option to indicate the desired interpreter. for obsolete versions of python like 3.7, this installs packages in terms of the system interpreter (apparently! maybe this is a bug in virtualenv. seems strange). This breaks immediately with current pip version on Python 3.7, see reproducer. using the venv backend, it uses the resolved interpreter all the way and things work correctly.

Expected Behavior

Assuming this is what virtualenv does, I would imagine we'd want to run virtualenv using the resolved interpreter also, not using the "-p" option. there seem to be options to virtualenv on how to install pip/setuptools also, but anyway, see the reproducer, something to ...document ? understand? not really sure.

Steps To Reproduce

noxfile:

import nox

@nox.session(python=["3.7"])
def helloworld(session: nox.Session) -> None:
    session.install("typing-extensions")

now make sure you have an old 3.7 interpreter and a new interpreter like 3.13 around.

make a py 3.13 environment and install nox using python 3.13:

[jenkins@9d93bd4d66c5 foo]$ /opt/python3.13/bin/python3 -m venv .venv
[jenkins@9d93bd4d66c5 foo]$ .venv/bin/pip install nox
Collecting nox
  Using cached nox-2025.10.16-py3-none-any.whl.metadata (5.1 kB)
  # ... packages install ...

Collecting virtualenv>=20.15 (from nox)
  Using cached virtualenv-20.35.3-py3-none-any.whl.metadata (4.6 kB)

# ... more
Installing collected packages: distlib, platformdirs, packaging, humanize, filelock, colorlog, attrs, argcomplete, virtualenv, dependency-groups, nox
Successfully installed argcomplete-3.6.3 attrs-25.4.0 colorlog-6.10.1 dependency-groups-1.3.1 distlib-0.4.0 filelock-3.20.0 humanize-4.14.0 nox-2025.10.16 packaging-25.0 platformdirs-4.5.0 virtualenv-20.35.3

[notice] A new release of pip is available: 25.2 -> 25.3
[notice] To update, run: /tmp/foo/.venv/bin/python3 -m pip install --upgrade pip

with verbose output you can see what versions of virtualenv etc. are present in the py3.13 environment. now make sure a Python 3.7 interpreter is in the PATH and run it:


[jenkins@9d93bd4d66c5 foo]$ PATH=/opt/python3.7/bin/:$PATH .venv/bin/nox -v
nox > Running session helloworld-3.7
nox > Creating virtual environment (virtualenv) using python3.7 in .nox/helloworld-3-7
created virtual environment CPython3.7.17.final.0-64 in 155ms
  creator CPython3Posix(dest=/tmp/foo/.nox/helloworld-3-7, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/jenkins/.local/share/virtualenv)
    added seed packages: pip==25.0.1, setuptools==75.3.2, wheel==0.45.1
  activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

nox > python -m pip install typing-extensions
Traceback (most recent call last):
  File "/opt/python3.7/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/opt/python3.7/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/__main__.py", line 22, in <module>
    from pip._internal.cli.main import main as _main
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/cli/main.py", line 11, in <module>
    from pip._internal.cli.autocompletion import autocomplete
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/cli/autocompletion.py", line 10, in <module>
    from pip._internal.cli.main_parser import create_main_parser
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/cli/main_parser.py", line 9, in <module>
    from pip._internal.build_env import get_runnable_pip
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/build_env.py", line 17, in <module>
    from pip._internal.cli.spinners import open_spinner
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/cli/spinners.py", line 9, in <module>
    from pip._internal.utils.logging import get_indentation
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_internal/utils/logging.py", line 13, in <module>
    from pip._vendor.rich.console import (
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_vendor/rich/console.py", line 40, in <module>
    from pip._vendor.typing_extensions import (
  File "/tmp/foo/.nox/helloworld-3-7/lib/python3.7/site-packages/pip/_vendor/typing_extensions.py", line 1039
    def TypedDict(typename, fields=_marker, /, *, total=True, closed=False, **kwargs):
                                            ^
SyntaxError: invalid syntax
nox > Command python -m pip install typing-extensions failed with exit code 1
nox > Session helloworld-3.7 failed.

What happened? in the verbose log we can see that virtualenv -p python3.7 installed pip==25.0.1 which is way too new for python 3.7 (looks like pip had a recent release, so maybe that introduced the problem). so it has a syntax issue and it fails. virtualenv seems to have some options on how to install pip, but this is anyway the default behavior the way nox is using that -p flag.

this is because nox runs virtualenv like this:

        if self.venv_backend == "virtualenv":
            cmd = [
                sys.executable,
                "-m",
                "virtualenv",
                self.location,
                "--no-periodic-update",
            ]
            if self.interpreter:
                cmd.extend(["-p", self._resolved_interpreter])

using sys.executable, which is python 3.13 here. "-p" seems to be some way to install the interpreter we want but it does not seem to care about the packages being incompatible with it, looking at the docs for -p I have a feeling this might not be how -p is supposed to be used, that is, to select a totally different interpreter far away in version and behavior from the one that is running virtualenv.

If we use the venv runner:

import nox

nox.options.default_venv_backend = 'venv'

@nox.session(python=["3.7"])
def helloworld(session: nox.Session) -> None:
    session.install("typing-extensions")

now we're good!

[jenkins@9d93bd4d66c5 foo]$ PATH=/opt/python3.7/bin/:$PATH .venv/bin/nox -v
nox > Running session helloworld-3.7
nox > Creating virtual environment (venv) using python3.7 in .nox/helloworld-3-7
nox > python -m pip install typing-extensions
Collecting typing-extensions
  Using cached typing_extensions-4.7.1-py3-none-any.whl (33 kB)
Installing collected packages: typing-extensions
Successfully installed typing-extensions-4.7.1

[notice] A new release of pip is available: 23.0.1 -> 24.0
[notice] To update, run: pip install --upgrade pip
nox > Session helloworld-3.7 was successful in 3 seconds.

how come? because venv uses the resolved_python interpreter to run:

        else:
            cmd = [self._resolved_interpreter, "-m", "venv", self.location]

so...I'm going to use the venv backend and hopefully that's it on this end, but, nox might want to adjust this or something. up to you! I'd document it at least....

Environment

- OS:
- Python:
- Nox:

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions