Skip to content

fix(dev-env): give the container its own Python venv - #1340

Merged
dekobon merged 1 commit into
mainfrom
fix/dev-env-container-venv
Aug 22, 2026
Merged

fix(dev-env): give the container its own Python venv#1340
dekobon merged 1 commit into
mainfrom
fix/dev-env-container-venv

Conversation

@dekobon

@dekobon dekobon commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Problem

The host and the dev container bind-mount the same checkout, so they
contended for a single big-code-analysis-py/.venv. A venv records
absolute interpreter paths in pyvenv.cfg and in every console-script
shebang, so one directory cannot serve both sides:

bash: .venv/bin/mypy: /home/dev/source/…/.venv/bin/python: bad interpreter

Whichever environment ran uv sync last owned it and the other got that
from py-typecheck, py-test and py-stubtest. make worktree-setup on
the host is enough to take ownership, so the two silently broke each
other — which is how this surfaced.

Fix

dev-env-run mounts a host cache directory over that path, so each side
gets its own virtualenv. The in-container path is still spelled .venv,
so none of the fourteen recipe lines resolving
$(BCA_PY_DIR)/.venv/bin/* change
and no gate mechanism is
refactored. Ownership lines up through the UID/GID build args
dev-env-build already passes.

Two consequences of the nested mount, both handled:

  • Docker creates a missing deeper mount target itself — as root, and
    on the host,
    since the path sits inside the bind mount. Verified in
    an isolated scratch mount: without a pre-created directory the host
    gets a root-owned .venv and host-side uv sync fails with
    permission denied. dev-env-run pre-creates it.
  • A mount point cannot be unlinked, so py-clean's rm -rf aborted
    the recipe with EBUSY inside the container, taking distclean with
    it. It now empties the directory in place when removal fails.
    Unchanged on the host, where .venv is an ordinary directory: removed
    as before, exit 0, and no mount-point message.

docker/README.md documents the split, the symptom it prevents, and the
host-side reset for a stale cached venv after a dev-env-rm /
dev-env-run cycle (the cache outlives the container, but the
uv-managed interpreter it names does not).

Verification

  • make pre-commitBCA_GATE: pass
  • make py-typecheck passing in the container and on the host
    simultaneously
    — mypy 32 files, pyright 0 errors on both sides. That
    state was previously impossible.
  • Container recreated from scratch against the final Makefile; container
    make py-clean exits 0; host .venv intact and user-owned.
  • py-clean exercised against a populated directory, an absent one, and
    a real mount point — exit 0 in all three.

Scope: dev tooling only. No library, CLI, or web code is touched.

Host and container bind-mount one checkout, so they contended for a
single `big-code-analysis-py/.venv`. A venv records absolute
interpreter paths in `pyvenv.cfg` and in every console-script
shebang, so the directory cannot serve both: whichever side ran
`uv sync` last owned it and the other got

    bash: .venv/bin/mypy: /…/.venv/bin/python: bad interpreter

from py-typecheck, py-test and py-stubtest. `make worktree-setup` on
the host is enough to take ownership, so the two environments
silently broke each other.

`dev-env-run` now mounts a host cache directory over that path. The
in-container path is still spelled `.venv`, so none of the fourteen
recipe lines resolving `$(BCA_PY_DIR)/.venv/bin/*` need to change,
and no gate mechanism is refactored. Ownership lines up through the
UID/GID args `dev-env-build` already passes.

Two consequences of the nested mount, both handled:

Docker mounts the repo first and then creates a missing deeper
target itself — as root, and on the host, since the path sits inside
the bind mount. Verified in an isolated scratch mount: without a
pre-created directory the host gets a root-owned `.venv` and
host-side `uv sync` fails with permission denied. `dev-env-run`
therefore pre-creates it.

A mount point cannot be unlinked, so `py-clean`'s `rm -rf` aborted
the recipe with EBUSY inside the container, taking `distclean` with
it. It now empties the directory in place when removal fails.
Unchanged on the host, where `.venv` is an ordinary directory:
removed as before, exit 0, and no mount-point message.

Verified with `make pre-commit` (BCA_GATE: pass) plus `py-typecheck`
passing in the container and on the host simultaneously — the state
that was previously impossible.
@dekobon
dekobon merged commit 212455c into main Aug 22, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant