feat: split environments from tasks (env:task) - #1135
Draft
henryiii wants to merge 3 commits into
Draft
Conversation
henryiii
force-pushed
the
henryiii/refactor/envrunner
branch
3 times, most recently
from
July 9, 2026 13:44
b4f5f0e to
1b2bb5b
Compare
henryiii
force-pushed
the
henryiii/refactor/envrunner
branch
2 times, most recently
from
July 31, 2026 14:16
d6e61d3 to
bb10ade
Compare
First step of the environment/task split: a new EnvRunner owns the virtualenv lifecycle (envdir, reuse decision, creation) with an idempotent ensure(), paired 1:1 with SessionRunner for now. SessionRunner keeps _create_venv/reuse_existing_venv/venv/envdir as delegating members, so behavior and the test-facing surface are unchanged. Assisted-by: ClaudeCode:claude-fable-5
Environments (venv + provisioning config) are now first-class and
separate from tasks:
- nox.env(name, python=..., venv_backend=..., ...) declares an
Environment; @env.task attaches tasks that share its single venv
(one EnvRunner per concrete environment instance, configured from
the environment rather than whichever task runs first).
- Sessions are env:task pairs. @nox.session is now sugar for a
same-name pair and keeps its exact naming, envdir, and expansion
behavior; historically legal names containing ':'/'(' keep working
with a FutureWarning.
- nox.alias(name, *targets) registers a global alias; aliases expand
recursively at selection time and warn when they shadow a task name.
- Environments and aliases are globally unique; task names only
within their environment.
- One identifier grammar everywhere: -s, requires=, and
session.notify() all accept env:task ids, aliases, environment or
instance selectors (which expand to default tasks; an environment
with no default tasks errors instead of silently selecting
nothing), and unambiguous bare task names. Instance selectors like
tooling-3.12 also work for single-python environments.
- Python lists expand at the environment level (tests-3.11:run);
@nox.parametrize stays task-level and shares the environment venv.
- Manifest construction errors surface as clean exit-3 messages.
- --json listing gains env and task fields.
The registry now stores envs/tasks/aliases (storage lives in
nox/environments.py so imports stay one-directional);
registry.get() keeps the legacy mapping view.
Assisted-by: ClaudeCode:claude-fable-5
Environments can now declare how they are provisioned:
- dependencies=[...] installs with pip/uv; an optional @env.setup hook
covers dynamic needs (making the environment unstampable unless
setup_stamp is set). Declaring dependencies on a venv-less
environment fails with a clear error.
- nox.env.pylock installs a PEP 751 pylock file via 'uv pip sync';
nox.env.uv syncs a uv project with 'uv sync --locked'. Both guard
the backend at sync time, so --force-venv-backend/--no-venv cannot
make 'uv sync' fall back to (and clobber) the project's own .venv.
Third parties subclass nox.Environment and override
stamp_data()/sync().
- A stamp file (.nox-env.json) inside the environment records the
provisioning inputs; when it matches on reuse, syncing is skipped
entirely. When it does not match, exact syncs (lock files) re-sync
in place, while additive ones (plain dependency lists) recreate the
environment so removed dependencies actually disappear
(Environment.sync_is_exact). Declarative environments reuse by
default; --reuse-venv=never still recreates, -R/--no-install still
skip installs. Classic sessions are unchanged.
- location=".venv" places the environment at a user path (relative to
the noxfile). Location collisions (including a missing
{name}/{python} placeholder on a multi-python environment) are
checked over the *selected* sessions at filter time, so a located
environment cannot break unrelated invocations under
--force-python. nox refuses to touch a non-empty location that is
not a virtual environment, and does not write
.gitignore/CACHEDIR.TAG outside --envdir.
- session.install() into a shared multi-task environment warns once
(unless the install is skipped by -R/--no-install), pointing at
declarative dependencies.
Assisted-by: ClaudeCode:claude-fable-5
henryiii
force-pushed
the
henryiii/refactor/envrunner
branch
from
August 1, 2026 02:28
bb10ade to
3f4f69d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP! This is the whole idea, so you can see the end goal, it's designed to be mergable in 3-4 stages.
🤖 AI text below 🤖
Splits the fused session concept into environments (a virtualenv + how to provision it) and tasks (what to run), so several tasks can share one venv, environments can live at a chosen path like
.venv, and lock files get first-class, extensible support.env:taskpair;@nox.sessionis sugar for a same-name pair, so existing noxfiles are unchanged (naming, envdirs, and expansion are byte-identical — verified against this repo's own noxfile).-s,requires=, andsession.notify(): full ids, aliases (recursive), environment/instance selectors (default tasks), and unambiguous bare task names; ambiguity errors list the candidates.-r/-R/--reuse-venvsemantics are untouched for classic sessions.location=safety: nox refuses to delete a non-empty directory that isn't a virtualenv, never writes.gitignore/CACHEDIR.TAGoutside--envdir, andnox.env.uvrefuses to sync off the uv backend so--force-venv-backendcannot clobber a project's own.venv.nox.Environmentand overridestamp_data()/sync()— no plugin registry needed.The four commits are ordered for review: runner split (no behavior change) → env/task/alias model → provisioning/lock files/locations → docs. An adversarial review pass (backend overrides, CLI edge cases, staleness lies, name collisions) was run and its findings are folded into the relevant commits with regression tests; end-to-end flows (uv.lock →
.venv, stamp skip/resync, ownership refusal) were exercised live in addition to the unit suite.Design decisions that deserve reviewer eyes: declarative-object API with
@env.setupas the dynamic escape hatch;nox -s <env>running default tasks; reuse-by-default for declarative envs only; provisioning as inline memoized calls on a shared EnvRunner rather than hidden graph nodes.