RUL-021 — Every feature-branch worktree owns a full environment, deleted at merge
Ruling
"every damn feature branch gets a full env until we merge then it gets deleted" (verbatim, 2026-07-27).
Concretely, for every git worktree holding a feature branch:
- Its
.venvis a real directory it owns. Never a symlink or hardlink to another checkout's virtualenv, and never a shared interpreter reached throughPYTHONPATHalone. - Its vendored build inputs are present locally.
vendor/wheels/is gitignored, and[tool.uv.sources]pins wheels by relative path, so a fresh worktree resolves those paths against an empty directory. Populate it (hardlinks cost no disk) before the firstuv sync. - At merge, the worktree and its environment are both deleted. A merged branch that still has a worktree is stale state, not a convenience.
This supersedes the earlier "link the main .venv into worktree builders for
speed" practice, which is now prohibited.
Evidence
Sharing one virtualenv across worktrees is not a saving; it is a shared mutable dependency graph with no owner.
- 2026-07-27,
wt-QWEN(SPEC-110)..venvwas a symlink to the main checkout's virtualenv, shared with the main checkout and two other worktrees. Itsvendor/wheels/was empty, so[tool.uv.sources] grpcio = { path = "vendor/wheels/grpcio-1.82.1-cp314-cp314t-linux_x86_64.whl" }resolved to nothing anduvfell back to building grpcio from sdist. A from-sdist grpcio carries nofreethreading_compatibleCython directive, so importing it re-enables the GIL process-wide — and it would have been installed into the virtualenv every other checkout uses. Oneuv syncwas between the repo and a SPEC-023 violation that would have reddened every future suite run everywhere, in a worktree whose own tests had not yet been written. - The branch also needed to add dependencies (
pytest-bdd, andlibcst/xxhashplanned). Adding a dependency to a shared virtualenv changes what every other checkout imports, with no record on any branch. - Precedent for the deletion half: task #11, "Delete stale worktree folders + branches after merge" — merged worktrees accumulated until they were swept by hand.
Enforcement
- gate scripts/check_worktree_envs.py RUL-021
Verification
scripts/check_worktree_envs.py enumerates git worktree list and fails
(exit 1) when any non-primary worktree:
- has a
.venvthat is a symlink, or that is missing while the worktree carries a branch; - declares a
[tool.uv.sources]relative path undervendor/that does not resolve inside that worktree; - holds a branch already merged into
master(stale — worktree and environment are due for deletion).
It is wired into scripts/health_check.py, so the standing "health check before
any bulk change or commit" step covers it. --fix-wheels populates missing
vendored wheels by hardlink; environment creation and worktree deletion stay
manual, because both destroy or build state a human should be choosing.