Skip to content

CircularChainError with base environment on set_env #3545

@samuelhwilliams

Description

@samuelhwilliams

Issue

I'm trying to set some default environment variables on env_run_base that will either pass through an existing environment variable value, or inject a default value.

This seems to always result in a CircularChainError. It only happens when the setenv is on the env_run_base, not when directly on the final environment. Is this a limitation of the base environment?

Environment

Provide at least:

  • OS: macos Darwin Mac.mynet 24.5.0 Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041 arm64
Output of pip list of the host Python, where tox is installed
uv pip list
Package           Version
----------------- -------
cachetools        6.0.0
chardet           5.2.0
colorama          0.4.6
distlib           0.3.9
exceptiongroup    1.3.0
filelock          3.18.0
iniconfig         2.1.0
packaging         25.0
platformdirs      4.3.8
pluggy            1.6.0
pygments          2.19.1
pyproject-api     1.9.1
pytest            8.4.0
tomli             2.2.1
tox               4.26.0
typing-extensions 4.14.0
virtualenv        20.31.2

Output of running tox

Output of tox -rvv
blah on  main is 📦 v0.1.0 via 🐍 v3.13.3tox
.pkg: recreate env because python changed version_info=[3, 9, 6, 'final', 0]->[3, 13, 3, 'final', 0] | executable='/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/bin/python3.9'->'/opt/homebrew/Cellar/[email protected]/3.13.3/Frameworks/Python.framework/Versions/3.13/bin/python3.13'
.pkg: remove tox env folder /Users/sam/git/blah/.tox/.pkg
.pkg: install_requires> python -I -m pip install 'setuptools>=40.8.0'
.pkg: _optional_hooks> python /opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_sdist> python /opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_sdist> python /opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
py39: install_package> python -I -m pip install --force-reinstall --no-deps /Users/sam/git/blah/.tox/.tmp/package/4/blah-0.1.0.tar.gz
py39: commands[0]> pytest -vvvs
================================================================================================================================================ test session starts =================================================================================================================================================
platform darwin -- Python 3.9.6, pytest-8.4.0, pluggy-1.6.0 -- /Users/sam/git/blah/.tox/py39/bin/python
cachedir: .tox/py39/.pytest_cache
rootdir: /Users/sam/git/blah
configfile: pyproject.toml
collected 1 item

test_envecho.py::test_env None
PASSED

================================================================================================================================================= 1 passed in 0.00s ==================================================================================================================================================
  py39: OK (3.39=setup[3.29]+cmd[0.10] seconds)
  congratulations :) (3.43 seconds)

blah on  main is 📦 v0.1.0 via 🐍 v3.13.3 took 3s

Then after setting setenv = { TESTENV = "{env:TESTENV:hello}" }

tox -rvv
py39: 84 W remove tox env folder /Users/sam/git/blah/.tox/py39 [tox/tox_env/api.py:333]
.pkg: 181 W remove tox env folder /Users/sam/git/blah/.tox/.pkg [tox/tox_env/api.py:333]
py39: 246 E internal error [tox/session/cmd/run/single.py:60]
Traceback (most recent call last):
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/session/cmd/run/single.py", line 47, in _evaluate
    tox_env.setup()
    ~~~~~~~~~~~~~^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/api.py", line 258, in setup
    self._setup_env()
    ~~~~~~~~~~~~~~~^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/runner.py", line 98, in _setup_env
    super()._setup_env()
    ~~~~~~~~~~~~~~~~~~^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/api.py", line 243, in _setup_env
    self.ensure_python_env()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/api.py", line 247, in ensure_python_env
    conf = self.python_cache()
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 82, in python_cache
    base = super().python_cache()
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/api.py", line 284, in python_cache
    "version_info": list(self.base_python.version_info),
                         ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/api.py", line 294, in base_python
    self._base_python = self._get_python(base_pythons)
                        ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 139, in _get_python
    interpreter = self.creator.interpreter
                  ^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 131, in creator
    return self.session.creator
           ^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 111, in session
    env = self.virtualenv_env_vars()
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 116, in virtualenv_env_vars
    env = self.environment_variables.copy()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/python/virtual_env/api.py", line 172, in environment_variables
    environment_variables = super().environment_variables
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/runner.py", line 193, in environment_variables
    environment_variables = super().environment_variables
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/tox_env/api.py", line 342, in environment_variables
    set_env: SetEnv = self.conf["set_env"]
                      ~~~~~~~~~^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/sets.py", line 122, in __getitem__
    return self.load(item)
           ~~~~~~~~~^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/sets.py", line 133, in load
    return config_definition.__call__(self._conf, self.loaders, ConfigLoadArgs(chain, self.name, self.env_name))  # noqa: PLC2801
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/of_type.py", line 106, in __call__
    value = loader.load(key, self.of_type, self.factory, conf, args)
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/api.py", line 148, in load
    converted = self.build(key, of_type, factory, conf, raw, args)
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/toml/__init__.py", line 68, in build
    exploded = Unroll(conf=conf, loader=self, args=args)(raw)
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/toml/_replace.py", line 71, in __call__
    res_dict[key] = self(val, depth)
                    ~~~~^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/toml/_replace.py", line 34, in __call__
    value = replace(self.conf, reference, value, self.args)
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/replacer.py", line 70, in replace
    return Replacer(conf, reference, conf_args=args, depth=depth).join(find_replace_expr(value))
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/replacer.py", line 199, in join
    return "".join(self(value))
                   ~~~~^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/replacer.py", line 196, in __call__
    return [self._replace_match(me) if isinstance(me, MatchExpression) else str(me) for me in value]
            ~~~~~~~~~~~~~~~~~~~^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/replacer.py", line 211, in _replace_match
    replace_value = replace_env(self.conf, args, conf_args)
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/loader/replacer.py", line 265, in replace_env
    set_env: SetEnv = env_conf.load("set_env", chain=conf_args.chain)
                      ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/sets.py", line 133, in load
    return config_definition.__call__(self._conf, self.loaders, ConfigLoadArgs(chain, self.name, self.env_name))  # noqa: PLC2801
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/tox/4.26.0/libexec/lib/python3.13/site-packages/tox/config/of_type.py", line 102, in __call__
    raise CircularChainError(msg)
tox.config.types.CircularChainError: circular chain detected env_run_base.setenv, env:TESTENV
  py39: FAIL code 2 (0.17 seconds)
  evaluation failed :( (0.19 seconds)

Minimal example

https://github.com/samuelhwilliams/tox-setenv-base

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions