Note on Bug discovery
Claude AI found this bug when I was trying to update openedx-platform to its latest requirements. I'd recommend to reproduce the bug first to verify that the bug report is accurate.
The rest of the bug ticket has been written with the help of Claude.
Bug description
If you mount a Python package into the devstack in order to work on it, the container stops
running your local copy and runs the released version from PyPI instead. Your edits then
have no effect on anything, and nothing anywhere reports an error.
The cause is that compiling Sass rewrites the virtualenv. edx-platform defines compile-sass
as uv run --active python scripts/compile_sass.py
(package.json#L11-L12),
and uv run does not simply run a command. It first syncs the virtualenv so that installed
packages match uv.lock, and only then runs what you asked for. A mounted package is an
editable install, which by definition does not match the lock, so uv replaces it.
Tutor calls that npm script during the image build and keeps a container running it on a
loop, so the sync happens repeatedly rather than once.
On whether this belongs here or in Open edX: the trigger lives in edx-platform, but the fix
belongs in Tutor. edx-platform chose to run its own scripts through uv run, which is
reasonable inside a repo whose environment uv manages. Tutor's image is a different
situation: the virtualenv at /openedx/venv is built and owned by Tutor, and Tutor is also
the thing that installs mounted packages as editable. Tutor is therefore the layer that has
to opt out of uv's environment management.
How to reproduce
Takes about 20 seconds and needs no mounts. This uses openedx-filters only because it is a
small package that edx-platform pins. Any package in edx-platform's uv.lock behaves the
same way.
tutor dev exec lms bash
cd /openedx/edx-platform
uv pip show openedx-filters | grep Version # note the locked version
uv pip install "openedx-filters==3.8.0"
uv pip show openedx-filters | grep Version # 3.8.0
uv run --active python -c "pass" # what compile-sass runs
uv pip show openedx-filters | grep Version # reverted to the locked version
Expected: running a Python script leaves installed packages alone.
Actual: the package is silently reverted.
Confirm the proposed fix neutralizes it:
uv pip install "openedx-filters==3.8.0"
UV_NO_SYNC=1 uv run --active python -c "pass"
uv pip show openedx-filters | grep Version # 3.8.0, preserved
No manual changes were made to the generated environment or config.yml.
Environment
- OS: macOS.
- Tutor: reproduced on 21.0.3-main. Code inspection confirms the same code path is still
present on main at febb38c3, but I have not built against main itself.
- edx-platform:
master at d1cdd628, after the uv migration.
- uv: 0.9.18, as pinned by Tutor in the Dockerfile.
Additional context
Suggested fix. Add this to the production stage of
tutor/templates/build/openedx/Dockerfile,
which development and final both inherit, so one line covers the image build and every
running container:
UV_NO_SYNC is the environment-variable form of --no-sync. It disables only uv run's
project sync. It does not affect uv pip install, and it does not affect the deliberate
uv sync --frozen in the python-requirements stage, because that is a separate build
stage and does not inherit an environment variable set in production.
Side benefit: it also removes a redundant dependency re-resolve on every build. On my setup
npm run build-dev went from 660s to 22.3s.
As a workaround today, the same line can be injected through the existing
openedx-dockerfile-pre-assets
patch hook, which sits immediately before the asset steps.
Note on Bug discovery
Claude AI found this bug when I was trying to update openedx-platform to its latest requirements. I'd recommend to reproduce the bug first to verify that the bug report is accurate.
The rest of the bug ticket has been written with the help of Claude.
Bug description
If you mount a Python package into the devstack in order to work on it, the container stops
running your local copy and runs the released version from PyPI instead. Your edits then
have no effect on anything, and nothing anywhere reports an error.
The cause is that compiling Sass rewrites the virtualenv. edx-platform defines
compile-sassas
uv run --active python scripts/compile_sass.py(package.json#L11-L12),
and
uv rundoes not simply run a command. It first syncs the virtualenv so that installedpackages match
uv.lock, and only then runs what you asked for. A mounted package is aneditable install, which by definition does not match the lock, so uv replaces it.
Tutor calls that npm script during the image build and keeps a container running it on a
loop, so the sync happens repeatedly rather than once.
On whether this belongs here or in Open edX: the trigger lives in edx-platform, but the fix
belongs in Tutor. edx-platform chose to run its own scripts through
uv run, which isreasonable inside a repo whose environment uv manages. Tutor's image is a different
situation: the virtualenv at
/openedx/venvis built and owned by Tutor, and Tutor is alsothe thing that installs mounted packages as editable. Tutor is therefore the layer that has
to opt out of uv's environment management.
How to reproduce
Takes about 20 seconds and needs no mounts. This uses
openedx-filtersonly because it is asmall package that edx-platform pins. Any package in edx-platform's
uv.lockbehaves thesame way.
Expected: running a Python script leaves installed packages alone.
Actual: the package is silently reverted.
Confirm the proposed fix neutralizes it:
No manual changes were made to the generated environment or
config.yml.Environment
present on
mainatfebb38c3, but I have not built againstmainitself.masteratd1cdd628, after the uv migration.Additional context
Suggested fix. Add this to the
productionstage oftutor/templates/build/openedx/Dockerfile,which
developmentandfinalboth inherit, so one line covers the image build and everyrunning container:
ENV UV_NO_SYNC=1UV_NO_SYNCis the environment-variable form of--no-sync. It disables onlyuv run'sproject sync. It does not affect
uv pip install, and it does not affect the deliberateuv sync --frozenin thepython-requirementsstage, because that is a separate buildstage and does not inherit an environment variable set in
production.Side benefit: it also removes a redundant dependency re-resolve on every build. On my setup
npm run build-devwent from 660s to 22.3s.As a workaround today, the same line can be injected through the existing
openedx-dockerfile-pre-assetspatch hook, which sits immediately before the asset steps.