Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tox-dev/tox
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4.41.0
Choose a base ref
...
head repository: tox-dev/tox
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4.42.0
Choose a head ref
  • 19 commits
  • 48 files changed
  • 2 contributors

Commits on Feb 19, 2026

  1. 🐛 fix(interrupt): suppress BrokenPipeError during teardown (#3778)

    When users interrupt tox with Ctrl-C, excessive tracebacks clutter the
    output and obscure the actual interrupt handling. The packaging backend
    process may already be terminated when teardown attempts to send the
    "_exit" shutdown message, causing BrokenPipeError exceptions that make
    it appear something went wrong when the interrupt is actually being
    handled correctly.
    
    The fix catches BrokenPipeError alongside SystemExit during backend
    teardown in the pyproject packaging code. 🐛 Both exceptions indicate the
    process is being terminated cleanly and don't require error reporting.
    This approach keeps the existing interrupt flow intact while eliminating
    noise during cleanup.
    
    Users will now see clean interrupt output focused on the actual teardown
    progress rather than pipe errors from attempting to communicate with
    already-dead processes.
    gaborbernat authored Feb 19, 2026
    Configuration menu
    Copy the full SHA
    903a4c5 View commit details
    Browse the repository at this point in the history
  2. ✨ feat(cli): add --no-capture flag for interactive programs (#3777)

    Interactive programs like Python REPL, debuggers, and TUI applications
    need direct terminal access to work correctly. They query console
    dimensions, set raw mode for keystroke input, and send VT100 escape
    sequences for cursor control. 🐛 Tox normally pipes stdout/stderr to
    capture output for logging and result reporting, but this breaks
    terminal APIs that require real console handles instead of pipe handles.
    Python 3.13+ REPL crashes on Windows with `WinError 123` when
    `_pyrepl/windows_console.py` tries to call Win32 APIs that only work on
    console buffers, and similar failures occur on other platforms when
    programs try to query terminal properties.
    
    The new `--no-capture` (`-i`) flag disables output capture and makes
    subprocesses inherit parent console handles directly. ✨ This is opt-in
    because it's mutually exclusive with `--result-json` where output must
    be captured, and parallel mode where output from multiple environments
    would interleave. The implementation validates incompatible flag
    combinations early with clear error messages. Additionally, `tox exec`
    now always runs with `no_capture` enabled since it's designed for
    one-off interactive commands, and the flag is hidden from its help
    output to avoid confusion.
    
    The flag works on all platforms since terminal APIs universally require
    console handles, not just on Windows. Documentation has been added to
    both the how-to guide for practical usage and the explanation section to
    clarify why interactive terminal programs need special handling.
    gaborbernat authored Feb 19, 2026
    Configuration menu
    Copy the full SHA
    cf64528 View commit details
    Browse the repository at this point in the history
  3. ✨ feat(config): add platform-dependent factor support (#3779)

    Managing cross-platform projects required creating separate test
    environments for each platform (like `task-linux`, `task-darwin`,
    `task-win32`) or using the `platform` configuration option which skips
    entire environments when the regex doesn't match. Neither approach
    allowed a single environment to run different commands based on the
    execution platform. 🔧
    
    This change injects `sys.platform` as an implicit factor available to
    all environments. Platform values like `linux`, `darwin`, and `win32`
    become automatically available alongside factors from the environment
    name, enabling natural conditional configuration that adapts at runtime.
    Users can now write `linux: pytest` or `win32: mypy` in a single
    environment without encoding the platform in the environment name.
    
    The implementation adds one line to `filter_for_env()` in
    `src/tox/config/loader/ini/factor.py` to include `sys.platform` in the
    factor set. This leverages the existing factor filtering infrastructure
    without requiring changes to how factors are parsed or matched. ✨
    
    Resolves #2092
    gaborbernat authored Feb 19, 2026
    Configuration menu
    Copy the full SHA
    97cf6e6 View commit details
    Browse the repository at this point in the history

Commits on Feb 20, 2026

  1. ✨ feat(env): gracefully skip environments with unavailable runners (#…

    …3781)
    
    When environments reference unavailable runners (from missing plugins),
    tox now gracefully skips them instead of failing catastrophically. 🔧 If
    a user explicitly requests an unavailable environment with `-e`, they
    get a clear error message indicating which runner is missing and that
    the plugin may not be installed.
    
    Environments discovered from config but not explicitly selected are
    shown with "NOT AVAILABLE" status without failing the overall run. This
    enables better plugin dependency management, allowing users to depend on
    optional plugins without breaking tox for users who haven't installed
    them.
    
    The implementation introduces a `RunnerUnavailable` exception to
    distinguish missing runners from other skip scenarios, extends
    `_ToxEnvInfo` to track unavailable environments, and updates the
    reporting layer to show "NOT AVAILABLE" in yellow (matching the SKIP
    status level). Success calculations treat unavailable environments like
    skipped—they don't cause the run to fail.
    
    Comprehensive TOML-based tests verify all scenarios: unavailable runners
    in discovered env_list, explicitly requested environments, mixed
    available/unavailable environments, and multiple unavailable runners.
    
    Fixes #3504
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    c82f253 View commit details
    Browse the repository at this point in the history
  2. 🐛 fix(env): restore compound factor conditionals (#3782)

    PR #3753 fixed section header names from being decomposed into freely
    combinable factors, preventing `tox -e functional-py312` from silently
    falling back to `[testenv]` when only `[testenv:functional{-py310}]` was
    defined. However, it also broke compound factor conditionals like
    `np-cov: coverage` — running `tox -e py310-np-cov` would fail with
    "provided environments not found" because the individual factor `cov`
    (from the compound conditional `np-cov`) was no longer recognized as
    combinable.
    
    🔍 The fix distinguishes between env names from section headers and env
    names discovered from factor conditionals. Section header names remain
    valid only as whole identifiers (preserving the #3753 fix), while
    factor-conditional env names have their individual factors added to the
    combinable set. This is done by collecting section-derived env names via
    `Config.sections()` and treating everything else in `known_envs` (minus
    `env_list`) as factor-conditional — splitting those into individual
    factors.
    
    Fixes #3780
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    e652010 View commit details
    Browse the repository at this point in the history
  3. ✨ feat(config): add default_base_python config key (#3783)

    Environments without a Python factor in their name (e.g. `lint`, `type`,
    `docs`) currently fall back to `sys.executable` when `base_python` isn't
    explicitly set. This means the interpreter varies across machines —
    Ubuntu 22.04 defaults to 3.10, Fedora 37 to 3.11 — harming
    reproducibility and causing unexpected failures when dependencies don't
    support the host's Python version. 🔧 Setting `base_python` globally in
    `[env_run_base]` works around this but conflicts with `pyXY`
    factor-named environments.
    
    The new `default_base_python` key sits at the environment config level
    and acts as a fallback only when no Python factor is detected and no
    explicit `base_python` is set. This gives it clear precedence semantics:
    Python factor > explicit `base_python` > `default_base_python` >
    `sys.executable`. Placing it in `[env_run_base]` rather than `[tox]`
    allows per-environment overrides when needed.
    
    ```toml
    [env_run_base]
    default_base_python = ["python3.10", "python3.9"]
    ```
    
    ✨ Existing configurations are unaffected since the default remains
    `sys.executable`. This closes #2846.
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    294a995 View commit details
    Browse the repository at this point in the history
  4. ✨ feat(config): add env_site_packages_dir_plat substitution (#3784)

    Since virtualenv 20.x, the \`lib64\` directory is no longer symlinked to
    \`lib\` on Linux distributions like Fedora and RHEL (see
    [virtualenv#1751](pypa/virtualenv#1751)). This
    means \`{env_site_packages_dir}\` (which returns purelib) doesn't cover
    the platform-specific \`lib64/pythonX.Y/site-packages\` path where
    compiled extensions live.
    
    🔧 The new \`{env_site_packages_dir_plat}\` /
    \`{envsitepackagesdir_plat}\` substitution exposes the platlib path from
    virtualenv's \`Describe\` interface. On most platforms (macOS, Windows)
    this is identical to purelib, but on affected Linux distributions it
    correctly resolves to the \`lib64\` directory.
    
    Closes #2302.
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    a8cc07f View commit details
    Browse the repository at this point in the history
  5. 🐛 fix(parallel): show --list-dependencies output (#3786)

    Running `tox run-parallel --list-dependencies` silently discards the
    dependency output on successful runs. This happens because parallel mode
    suspends all stdout/stderr into in-memory buffers, and those buffers are
    only flushed when a test fails or when `parallel_show_output` is
    explicitly enabled. Since `--list-dependencies` writes via
    `logging.warning()`, the output gets captured and never reaches the
    user.
    
    The fix adds `list_dependencies` as an additional condition for flushing
    the suspended output buffers after each environment completes. This sits
    alongside the existing `parallel_show_output` and failure checks in
    `_handle_one_run_done`, preserving the same flush-or-discard pattern
    while ensuring dependency output is always visible when requested.
    
    Fixes #3322
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    6885b5c View commit details
    Browse the repository at this point in the history
  6. ✨ feat(config): add open-ended generative ranges (#3788)

    Projects that test across multiple Python versions must manually update
    `env_list` whenever a new CPython release comes out. This is easy to
    forget and creates unnecessary churn in configuration files. The
    existing closed-range syntax `py3{10-13}` helps, but still requires
    bumping the upper bound each release cycle.
    
    Open-ended ranges solve this by letting users write `py3{10-}` or
    `py3{-13}`, where the missing bound is filled in from two constants —
    `LATEST_PYTHON_MINOR_MIN` (oldest non-EOL minor, currently 10) and
    `LATEST_PYTHON_MINOR_MAX` (latest stable release, currently 14) — that
    are updated with each tox release. The expansion is purely syntactic and
    happens at configuration load time with no I/O or interpreter probing,
    keeping it deterministic and fast. Environments for interpreters not
    installed on the system are naturally handled by
    `skip_missing_interpreters`.
    
    This is a **breaking change** for the previously-documented behavior
    where `{N-}` and `{-N}` were treated as literals. Non-numerical open
    ranges like `{a-}` and `{-b}` remain unexpanded.
    
    Fixes #3583
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    4573c04 View commit details
    Browse the repository at this point in the history
  7. 🐛 fix(config): collapse continuation lines before factor filtering (#…

    …3787)
    
    When a factor-specific command uses backslash continuation (`\`) to span
    multiple lines, the continuation lines leak into environments that don't
    match the factor. For example with `foo: python -c "\⏎ print('foo')"`,
    the `print('foo')"` line gets executed as a standalone command in the
    `bar` environment because `expand_factors()` sees it as an unfactored
    line.
    
    🔧 The root cause is the ordering of operations in `process_raw()` —
    factor filtering via `filter_for_env()` splits by newlines and evaluates
    each line independently, but backslash continuation collapsing (`\\\n`
    removal) happened *after* filtering. Moving the continuation collapse to
    happen *before* factor filtering ensures multi-line commands are joined
    into a single line before the factor prefix check runs.
    
    This is an INI-only issue since TOML configs don't use
    `filter_for_env()`.
    
    Fixes #2912
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    78eb394 View commit details
    Browse the repository at this point in the history
  8. ✨ feat(pkg): cache external packaging env build across envs (#3790)

    When using `package = external` with a shared `package_env`, the build
    commands run once per test environment that depends on it, rebuilding an
    identical package each time. For projects compiling C extensions or
    running complex build pipelines, this wastes significant time — often
    several minutes per redundant build.
    
    🚀 The built package path is now cached after the first successful build
    within a session. Subsequent test environments that share the same
    external packaging env skip the build entirely and reuse the
    already-built artifact. Per-environment extras and dependencies are
    still resolved individually, so environments with different `extras`
    configurations continue to get the correct dependency set.
    
    This mirrors the caching behavior that `wheel_build_env` already
    provides for PEP-517 wheel builds, extending the same optimization to
    external packaging workflows.
    
    Fixes #2729
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    326e14a View commit details
    Browse the repository at this point in the history
  9. ✨ feat(changelog): add granular towncrier categories (#3785)

    The previous 5 changelog categories (feature, bugfix, doc, removal,
    misc) lacked granularity for communicating the nature of changes to
    users. Deprecations and breaking changes were scattered across unrelated
    sections, making it harder for users to assess upgrade impact at a
    glance.
    
    This adopts 8 categories inspired by [yarl's
    changelog](https://yarl.aio-libs.org/en/latest/changes/): `breaking`,
    `deprecation`, `feature`, `bugfix`, `doc`, `packaging`, `contrib`, and
    `misc`. Each category now has a more descriptive display name (e.g. "Bug
    fixes" instead of "Bugfixes", "Miscellaneous internal changes" instead
    of "Miscellaneous"). The unused `removal` directory is replaced by
    `breaking` for backward incompatible changes, while `deprecation` gets
    its own dedicated section for upcoming removals.
    
    Fixes #3200
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    80d2931 View commit details
    Browse the repository at this point in the history
  10. ✨ feat(cli): accept plugin CLI options during provisioning (#3791)

    Plugins that add CLI options via `tox_add_option` couldn't be used with
    `requires`-based provisioning. Running `tox --demo-plugin` where
    `--demo-plugin` is defined by a plugin in `requires` would fail with
    "unrecognized arguments" because CLI argument validation happened before
    provisioning had a chance to install the plugin and re-execute tox.
    
    🔧 The fix defers unknown argument validation until after the
    provisioning check. During initial CLI parsing, `parse_known_args` is
    used instead of `parse_args`, allowing unrecognized flags through. If
    provisioning is needed, tox re-executes in the provisioned environment
    where the plugin is installed and the flag is recognized. If
    provisioning is *not* needed, any remaining unknown arguments still
    raise the familiar error message — so typos and invalid flags are still
    caught.
    
    The demo plugin used in integration tests now also registers a
    `--demo-plugin` CLI flag via `tox_add_option` to exercise this path.
    
    Fixes #2935
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    0c08af3 View commit details
    Browse the repository at this point in the history
  11. ✨ feat(env): add recreate_commands config key (#3793)

    When `tox run -r` recreates an environment, external caches managed by
    tools inside that environment (e.g. pre-commit) survive the directory
    wipe. Users had no mechanism to clean those caches as part of the
    recreation lifecycle, leading to stale state that defeats the purpose of
    recreation.
    
    🧹 `recreate_commands` is a new `list[Command]` config key that runs
    inside the still-existing environment before its directory is removed.
    This placement is deliberate -- the old virtualenv and its installed
    tools remain available, so commands like `{env_python} -Im pre_commit
    clean` work naturally. The env's bin directory is temporarily added to
    the allowlist so command resolution works correctly even though
    `_setup_env` hasn't run yet in the current session.
    
    Failures in `recreate_commands` are logged as warnings but never block
    recreation -- the environment is being destroyed anyway, so a cleanup
    failure shouldn't prevent a fresh start. The commands are skipped
    entirely on first creation (no env directory exists yet) and on normal
    re-runs without `-r`.
    
    Closes #3423
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    ae36881 View commit details
    Browse the repository at this point in the history
  12. 📝 docs(faq): document Debian python3-venv limitation (#3789)

    On Debian and Ubuntu the system Python splits `venv` and `ensurepip`
    into a separate `python3-venv` package. tox itself is unaffected — it
    uses `virtualenv` which bundles its own bootstrap — but tools that tox
    runs as commands inside environments (most commonly `pyproject-build`)
    may use stdlib `venv` internally and fail with a confusing "ensurepip is
    not available" error.
    
    This adds a "Known limitations" entry in the concepts documentation
    explaining the root cause and providing two solutions: installing
    `python3-venv`, or using `tox-uv` to bypass stdlib `venv` entirely.
    
    Closes #3195
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    5ee23bf View commit details
    Browse the repository at this point in the history
  13. ✨ feat(config): warn on unused config keys with -v (#3795)

    Config directives placed in the wrong section (e.g.
    `ignore_base_python_conflict` in `[testenv]` instead of `[tox]`) are
    silently ignored. The only way to discover this was via `tox config` and
    inspecting the `# !!! unused:` markers — something new users are
    unlikely to know about.
    
    This surfaces unused config key warnings during `tox run -v`, printing
    them in yellow before the final report. The warnings are gated behind
    `-v` (one level above default verbosity) so existing workflows are
    unaffected. This also cannot be an error because unused keys can be
    legitimate — for example, plugin-defined keys that only exist in a
    provisioned environment.
    
    Documentation updated across all four Diataxis dimensions:
    - **Reference** (`config.rst`): added notes to Core and tox environment
    section headers clarifying section boundaries
    - **Explanation** (`explanation.rst`): new "Misplaced configuration
    keys" section in Known limitations
    - **How-to** (`usage.rst`): added step 6 to troubleshooting guide
    - **Tutorial** (`getting-started.rst`): added tip after first section
    explanation
    
    Fixes #3188
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    ee25113 View commit details
    Browse the repository at this point in the history
  14. ✨ feat(cli): add --skip-env-install flag for offline env reuse (#3792)

    When working offline or in CI environments where dependencies are
    already installed, users had no way to skip the dependency installation
    step without also losing package installation control. The existing
    `--skip-pkg-install` flag only skips package installation but still
    attempts to install dependencies, which fails without network access.
    This addresses #3310.
    
    The new `--skip-env-install` flag skips both dependency installation
    (`deps`, dependency groups) and package installation in a single opt-in
    flag. When set, `_setup_env()` returns early before calling
    `_install_deps()` and `_install_dependency_groups()`, and
    `_setup_with_env()` treats it as implying `--skip-pkg-install` to also
    skip package building. This makes it straightforward to reuse a
    previously populated environment without any network calls.
    
    The flag is available on all run commands (`run`, `run-parallel`,
    `legacy`) alongside the existing `--skip-pkg-install` flag. Existing
    behavior is completely unchanged when the flag is not provided.
    
    Fixes #3310
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    7ea5bc1 View commit details
    Browse the repository at this point in the history
  15. ✨ feat(env): add virtualenv_spec for per-env version pinning (#3794)

    No single virtualenv release covers the full range of Python versions
    that projects need to test against — older versions like
    `virtualenv<20.22.0` are required for Python 3.6, while the latest
    virtualenv is needed for Python 3.15+. Since tox imports virtualenv as a
    library, environments targeting unsupported Python versions simply fail
    with no workaround. ✨ This becomes a real blocker for projects that must
    maintain compatibility across a wide Python version range in a single
    `tox.toml`.
    
    The new `virtualenv_spec` per-environment config key (e.g.
    `virtualenv_spec = "virtualenv<20.22.0"`) solves this by bootstrapping
    the specified virtualenv version into a cached venv under
    `.tox/.virtualenv-bootstrap/` and driving it via subprocess instead of
    the imported library. The bootstrap is content-addressed by spec hash,
    protected by file locks for concurrent safety, and reused across runs.
    When `virtualenv_spec` is empty (the default), tox continues using the
    imported virtualenv with zero overhead — the subprocess path only
    activates when explicitly configured.
    
    The `VirtualEnv` class now operates in dual mode: its `session` property
    routes to either the imported `session_via_cli` or a new
    `SubprocessSession` that mimics the same `Session`/`Creator`/`Describe`
    interface. Path accessors (`bin_dir`, `purelib`, `exe`, etc.) dispatch
    through `isinstance` checks so both modes integrate transparently with
    the rest of the environment lifecycle. The `python_cache()` dict
    includes the spec string when set, ensuring environments are
    automatically recreated when the pinned version changes.
    
    Closes #3656
    gaborbernat authored Feb 20, 2026
    Configuration menu
    Copy the full SHA
    c80c62f View commit details
    Browse the repository at this point in the history
  16. release 4.42.0

    gaborbernat committed Feb 20, 2026
    Configuration menu
    Copy the full SHA
    09b4f60 View commit details
    Browse the repository at this point in the history
Loading