Repository: wntrblm/nox (python)

Issue:
session.conda_install fails when specifying min/max version with newer conda
### Current Behavior

nox > Running session flake8
nox > Creating conda env in .nox/flake8 with python=3.9
nox > conda install --yes --prefix /home/jeff/workspace/nox_test/.nox/flake8 '"flake8>=4.0.1"'
nox > Command conda install --yes --prefix /home/jeff/workspace/nox_test/.nox/flake8 '"flake8>=4.0.1"' failed with exit code 1:

InvalidMatchSpec: Invalid spec '"flake8>=4.0.1"': Invalid version '4.0.1"': invalid character(s)

nox > Session flake8 failed.

### Expected Behavior

I expect the package to be installed when specifying a min or max version

### Steps To Reproduce

noxfile.py:
```
import nox

@nox.session(python='3.9', venv_backend='conda')
def flake8(session):
    session.conda_install('flake8>=4.0.1')
    session.install('--no-deps', '-e', '.')
    session.run('flake8')
```

1. Install a new miniconda version (https://repo.anaconda.com/miniconda/Miniconda3-py312_25.1.1-2-Linux-x86_64.sh)
2. conda create -n nox python=3.11 nox
3. conda activate nox -y
4. nox

### Environment

```Markdown
- OS: Ubuntu 24.04.2
- Python: 3.12.9
- Nox: 2025.2.9
- Conda: 25.1.1
```

### Anything else?

Temporary workaround:
`nox.sessions._dblquote_pkg_install_args = lambda args: args`

You appear to routing this through [subprocess.Popen](https://docs.python.org/3/library/subprocess.html#security-considerations) which states:

> Unlike some other popen functions, this library will not implicitly choose to call a system shell. This means that all characters, including shell metacharacters, can safely be passed to child processes.

I believe that this quoting can be removed completely: https://github.com/wntrblm/nox/blob/main/nox/sessions.py#L726-L727

Fails with:
- conda 25.1.1
- conda 24.11.1

Works with:
- conda 24.9.2

Pull request:
fix: conda_install issue with newer conda
Fix #951.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index df71304..e91cb1e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,12 +24,14 @@ jobs:
         os: [ubuntu-22.04, windows-latest, macos-13]
         python-version:
           - "3.8"
-          - "3.9"
           - "3.10"
-          - "3.11"
           - "3.12"
           - "3.13"
         include:
+          - os: ubuntu-24.04
+            python-version: "3.9"
+          - os: ubuntu-24.04
+            python-version: "3.11"
           - os: macos-14
             python-version: "3.12"
     steps:
@@ -60,6 +62,10 @@ jobs:
         run:  uv pip install --system .
       - name: Run tests on ${{ matrix.os }}
         run: nox --non-interactive --error-on-missing-interpreter --session "tests-${{ matrix.python-version }}" -- --full-trace
+
+      - name: Run Conda tests
+        if: matrix.python-version == '3.12'
+        run: nox --non-interactive --error-on-missing-interpreter --session "conda_tests" -- --full-trace
       - name: Save coverage report
         uses: actions/upload-artifact@v4
         with:
diff --git a/nox/sessions.py b/nox/sessions.py
index c278539..8a40cfb 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -724,7 +724,8 @@ class Session:
             return
 
         # Escape args that should be (conda-specific; pip install does not need this)
-        args = _dblquote_pkg_install_args(args)
+        if sys.platform.startswith("win32"):
+            args = _dblquote_pkg_install_args(args)
 
         if silent is None:
             silent = True
diff --git a/noxfile.py b/noxfile.py
index 6dc05cf..2737247 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -74,6 +74,9 @@ def conda_tests(session: nox.Session) -> None:
         "--file", "requirements-conda-test.txt", channel="conda-forge"
     )
     session.install("-e.", "--no-deps")
+    # Currently, this doesn't work on Windows either with or without quoting
+    if not sys.platform.startswith("win32"):
+        session.conda_install("requests<99")
     session.run("pytest", *session.posargs)
 
 
@@ -84,6 +87,8 @@ def mamba_tests(session: nox.Session) -> None:
         "--file", "requirements-conda-test.txt", channel="conda-forge"
     )
     session.install("-e.", "--no-deps")
+    if not sys.platform.startswith("win32"):
+        session.conda_install("requests<99")
     session.run("pytest", *session.posargs)
 
 
@@ -93,7 +98,10 @@ def micromamba_tests(session: nox.Session) -> None:
     session.conda_install(
         "--file", "requirements-conda-test.txt", channel="conda-forge"
     )
+    # Currently, this doesn't work on Windows either with or without quoting
     session.install("-e.", "--no-deps")
+    if not sys.platform.startswith("win32"):
+        session.conda_install("requests<99")
     session.run("pytest", *session.posargs)
 
 


Test patch:
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index 50c072e..91ebab5 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -581,6 +581,7 @@ class TestSession:
         with pytest.raises(ValueError, match="arg"):
             session.conda_install()
 
+    @pytest.mark.skipif(not sys.platform.startswith("win32"), reason="Only on Windows")
     def test_conda_install_bad_args_odd_nb_double_quotes(self) -> None:
         session, runner = self.make_session_and_runner()
         runner.venv = mock.create_autospec(nox.virtualenv.CondaEnv)
@@ -590,6 +591,7 @@ class TestSession:
         with pytest.raises(ValueError, match="odd number of quotes"):
             session.conda_install('a"a')
 
+    @pytest.mark.skipif(not sys.platform.startswith("win32"), reason="Only on Windows")
     def test_conda_install_bad_args_cannot_escape(self) -> None:
         session, runner = self.make_session_and_runner()
         runner.venv = mock.create_autospec(nox.virtualenv.CondaEnv)
@@ -646,7 +648,7 @@ class TestSession:
             else:
                 args += [f"--channel={c}" for c in channel]
             session.conda_install(
-                "requests", "urllib3", auto_offline=auto_offline, channel=channel
+                "requests<99", "urllib3", auto_offline=auto_offline, channel=channel
             )
             run.assert_called_once_with(
                 conda,
@@ -655,7 +657,7 @@ class TestSession:
                 *args,
                 "--prefix",
                 "/path/to/conda/env",
-                "requests",
+                '"requests<99"' if sys.platform.startswith("win32") else "requests<99",
                 "urllib3",
                 **_run_with_defaults(silent=True, external="error"),
             )
@@ -716,7 +718,9 @@ class TestSession:
 
         if version_constraint == "no":
             pkg_requirement = passed_arg = "urllib3"
-        elif version_constraint == "yes":
+        elif version_constraint == "yes" and not sys.platform.startswith("win32"):
+            pkg_requirement = passed_arg = "urllib3<1.25"
+        elif version_constraint == "yes" and sys.platform.startswith("win32"):
             pkg_requirement = "urllib3<1.25"
             passed_arg = f'"{pkg_requirement}"'
         elif version_constraint == "already_dbl_quoted":
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
index f2d532b..e71313e 100644
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -218,16 +218,16 @@ def test_condaenv_create_with_params(
 def test_condaenv_create_interpreter(
     make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
-    venv, dir_ = make_conda(interpreter="3.8")
+    venv, dir_ = make_conda(interpreter="3.12")
     venv.create()
     if IS_WINDOWS:
         assert dir_.joinpath("python.exe").exists()
-        assert dir_.joinpath("python38.dll").exists()
-        assert dir_.joinpath("python38.pdb").exists()
-        assert not dir_.joinpath("python38.exe").exists()
+        assert dir_.joinpath("python312.dll").exists()
+        assert dir_.joinpath("python312.pdb").exists()
+        assert not dir_.joinpath("python312.exe").exists()
     else:
         assert dir_.joinpath("bin", "python").exists()
-        assert dir_.joinpath("bin", "python3.8").exists()
+        assert dir_.joinpath("bin", "python3.12").exists()
 
 
 @has_conda