unset VIRTUALENV # don't leak a foreign venv into uv
Root cause. The worker's uv lock/uv sync ran against a 9h-old index snapshot. The judge re-runs pip list --outdated against live PyPI, so it sees whatever shipped after the snapshot — here charset-normalizer 3.5.1 and mypy 2.3.1. Criterion 4 ("no other outdated") is an allowlist check: only the 4 intentional blockers + ruff may appear outdated. Two unexpected rows → FAIL. The lockfile wasn't wrong; it was stale by the time the judge ran.
Fix — bump only the drifted patches (targeted re-resolution, not a blanket upgrade):
# in the project dir, from the cron/worker shell:
unset VIRTUAL_ENV # don't leak a foreign venv into uv
uv lock --upgrade-package charset-normalizer --upgrade-package mypy
uv sync
# uv sync PRUNED ad-hoc pip/ruff/requests-mock -> restore before verifying:
uv pip install pip ruff==0.6.9 requests-mock
python -m pip list --outdated --format=json # expect exactly 4 blockers + ruff
--upgrade-package re-resolves only those two against the live index (log shows Updated charset-normalizer v3.5.0 -> v3.5.1 / Updated mypy v2.3.0 -> v2.3.1), leaving the pinned blockers untouched and everything else byte-identical.
Two hard-won rules (both reproduced below):
1. Restore after every sync. uv sync makes the venv exactly equal the lockfile — any package installed outside it (ruff, pip, ad-hoc opencv/requests-mock) is uninstalled. Restore before any pip-based verification or tool run.
2. Unset VIRTUAL_ENV in cron shells. A leftover VIRTUAL_ENV pointing elsewhere makes uv warn/misbehave (does not match the project environment path .venv and will be ignored in uv 0.11.17; other versions error or target the wrong env). unset VIRTUAL_ENV makes the sync deterministic.
Judge criterion 4 as an allowlist check (what the fix must satisfy):
import json, subprocess, sys
ALLOWED = {"certifi", "idna", "requests", "urllib3", "ruff"} # 4 blockers + ruff
out = json.loads(subprocess.run(
[sys.executable, "-m", "pip", "list", "--outdated", "--format=json"],
capture_output=True, text=True).stdout)
unexpected = {p["name"]: f'{p["version"]}->{p["latest_version"]}' for p in out
if p["name"] not in ALLOWED}
assert not unexpected, f"criterion 4 FAIL: {unexpected}" # PASS iff empty
Full reproduction at `/tmp/deps002-demo/run.sh` (project with 4 pinned blockers + range-pinned `charset-normalizer>=3.4.0`, `mypy>=2.2.0`, `requests-mock`; temporary `<3.5.1`/`<2.3.1` caps simulate the snapshot index the worker saw). Python 3.14, uv 0.11.17, live PyPI.
**Judge #1 — FAIL (exit 1), exactly the reported failure:**
```
outdated (7): {'certifi': '2024.7.4 -> 2026.7.22', 'charset-normalizer': '3.5.0 -> 3.5.1',
'idna': '3.7 -> 3.18', 'mypy': '2.3.0 -> 2.3.1', 'requests': '2.32.3 -> 2.34.2',
'ruff': '0.6.9 -> 0.16.3', 'urllib3': '2.2.2 -> 2.7.0'}
CRITERION 4: FAIL -- unexpected outdated: {'charset-normalizer': '3.5.0 -> 3.5.1',
'mypy': '2.3.0 -> 2.3.1'}
```
**Fix + judge #2 — PASS (exit 0):**
```
Updated charset-normalizer v3.5.0 -> v3.5.1
Updated mypy v2.3.0 -> v2.3.1
outdated (5): {'certifi', 'idna', 'requests', 'ruff', 'urllib3'} # 4 blockers + ruff only
CRITERION 4: PASS -- no other outdated
```
**Prune pitfall (live):** the fix's `uv sync` uninstalled `pip==26.2.1` and `ruff==0.6.9` (sync log `- pip`, `- ruff`); post-sync check showed `pip: PRUNED by sync`, `ruff: PRUNED by sync`, while declared dep `requests-mock: 1.12.1` was kept. Restoring with `uv pip install pip ruff==0.6.9 requests-mock` brought the toolchain back.
**Edge cases tested:**
- *pip itself gets pruned* — my first judge-#2 run crashed (`python -m pip` gone → JSONDecodeError on empty output). This *is* the pitfall in action: restore `pip` before any verification.
- *Ad-hoc extras (opencv/requests-mock)*: same prune fate as ruff/pip unless declared in the lockfile or installed via `uv sync --extra <group>`; the demo shows the keep-vs-prune boundary is "in lockfile or not".
- *VIRTUAL_ENV trap*: with `VIRTUAL_ENV=/tmp/.../foreign-venv`, uv 0.11.17 warns and ignores it (exit 0) — safe in this version, but behavior varies across uv versions, so `unset VIRTUAL_ENV` is the deterministic fix.
- *Blockers stay put*: `--upgrade-package` moved only the 2 named packages; certifi/idna/requests/urllib3 remained exactly at pins before and after.
- *No blanket `uv lock --upgrade`*: avoided, since that would re-resolve everything and could break the blocker pins.
---{"model": "deepseek-v4-flash", "problem_class": "python-deps-batch-upgrade-judge-snapshot-drift", "result": "passed", "tests": 6}