Repository navigation
CRITICAL(update): the venv provenance marker survives a pip upgrade and bricks the package — 12 husk dist-info dirs already block deploys #17332
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 23, 2026 Fix pushed in #17338 (the provenance-husk half), head
a4b2e7a0e5.Not closed, and the reason matters more than the fix. The last criterion here is a multi-host provisioning run, which this session cannot perform. The failure was observed on one, the cause is read from the code and from pip's own source, but the fixed run has not happened. Closure needs that run's output, not this merge.
Correction: 13 husks across two venvs, not 12 in one
The count in this issue was measured in
autobot-backend/venvalone and written as "12 packages on this host" — a one-venv measurement reported with host-wide language. A reader greps that venv, confirms 12, and stops.Found by autobot-ai-f8, who swept every venv rather than the one this issue named. Re-measured here independently before correcting anything, read-only, using the same predicate
is_provenance_huskapplies (a.dist-infowhose only entry isAUTOBOT_PROVENANCE):venv husks autobot-backend/venv12 autobot-slm-backend/venv1 — psycopg2_binary-2.9.12.dist-infoai-stack, browser-worker, npu-worker, slm-agent, tts-worker 0 The thirteenth matters beyond the arithmetic. It is in the SLM's own venv — the component that runs the builtin updater, which is the only path
clear_provenance_husksis wired into (#17339). Its live siblingpsycopg2_binary-2.9.13.dist-infocarries a 46-line RECORD while the husk holds one file, so it is the same upgrade-leak mechanism, not a second defect.f8 also ran the predicate as a standalone dry-run over every
*.dist-infounder the install tree: 13 matched, 0 near-misses. Nothing on this host sits near the boundary, so the narrow predicate costs no coverage here while keeping the deletion defensible.Corrected in the branch (
e70493c24f) in both docstrings that carried the number, and in #17338's body. The code is unchanged — the defect and the fix are the same; only the measurement was wrong.- added 10 commits that reference this issue
on Sep 23, 2026 All four criteria met. PR #17338 merged
193b182c79at 02:10. Verified against merged code withgit show origin/main:, not against the PR diff.- The marker can no longer leave a husk.
venv_provenance.py:171_record_marker_in_recordappends the marker to the distribution's ownRECORD, so pip's uninstall removes it and can then rmdir the directory. The append format was checked against pip's own convention on a real installed distribution —PyNaCl-1.5.0.dist-info/RECORD,,, i.e.dist-info-name/filenamewith empty hash and size — so pip'siter_declared_entries()will pick it up rather than skip a malformed line. - The reconciler detects and clears an existing husk.
is_provenance_huskandclear_provenance_husksare both on main. The predicate is conjunctive — directory, no METADATA, no RECORD,iterdir()succeeds, and contents are exactly[AUTOBOT_PROVENANCE]— so anything carrying another file fails it and is left alone.OSErroroniterdirfails closed;OSErroron unlink/rmdir is logged and skipped rather than retried. - A test installs, stamps, upgrades, and asserts no contentless
dist-info.test_a_stamped_install_leaves_no_husk_when_it_is_upgraded(:109), withtest_the_old_unrecorded_marker_reproduces_the_husk(:92) as its positive control — the pair is what makes it meaningful, since the second proves the harness can actually produce the defect the first asserts is absent. -
has_tool_provenanceis not fooled by a husk.venv_provenance.py:158-168returnsnot is_provenance_husk(dist_info), with the reasoning in the docstring: "answering True would let a removal be authorised by the debris of an earlier one." Pinned bytest_has_tool_provenance_is_false_for_a_husk(:193).
Ten tests in
test_venv_provenance_husk_17332.py, includingtest_a_real_installation_is_never_clearedandtest_a_directory_holding_anything_else_is_left_alone— the two that make the deletion defensible rather than merely narrow.What this does NOT do, recorded so nobody reads the closure as broader than it is. The 13 husks measured on a live host are still on disk. The repair is reachable only from
_run_pip_install(the builtin code-sync path);git grepacrossautobot-slm-backend/ansible/returns zero references to it, and that host is ansible-provisioned. #17339 stays open and is the issue that repairs an already-husked host. This one closes because the defect that creates husks is fixed and existing ones are cleared on the path that can see them.One narrow fail-open found in review and filed as #17357, not a blocker:
write_provenance_markerwrites the marker before recording it, and recording silently no-ops whenRECORDis absent or unwritable — recreating this defect for that one package. It is strictly narrower thanmain's previous behaviour, which never recorded the marker at all, and none of the 13 observed husks were of that shape. It is honestly tested as intended behaviour (test_a_distribution_without_a_record_is_still_stamped) rather than hidden, which is why it is a fast-follow and not a revert.- The marker can no longer leave a husk.
The
#15067install-provenance marker is written into a distribution's owndist-infodirectory and is not listed in that distribution's
RECORD. pip only deletes whatRECORDlists, so on the next upgrade the old
dist-infodirectory survives as a husk containing onefile: the marker. Python then sees a second, contentless distribution for that package with
version
None, and every later pip run that must uninstall it aborts.The venv cannot be repaired by re-running the deploy — the failing step is the deploy.
Evidence — a provisioning run from this evening
On-disk state in that venv:
cachetools-7.1.8.dist-info/AUTOBOT_PROVENANCE:{"tool": "autobot-venv-reconcile", "component": "autobot-backend", "recorded_at": "2026-09-20T11:36:20+00:00"}This is not one package. A sweep of that venv's
site-packagesfound 12dist-infodirectories holding exactly one file:
Every one of those packages is now un-upgradable by pip on that node. pip stops at the first,
so a run surfaces them one at a time — 12 consecutive failed deploys to discover them all.
Root cause
autobot-slm-backend/venv_provenance.py:115(write_provenance_marker) writesAUTOBOT_PROVENANCEinside the target'sdist-infodirectory. Nothing adds it toRECORD, andnothing removes it before an upgrade.
The module's own docstring states the assumption that fails
(
autobot-slm-backend/venv_provenance.py:35): a reinstall "replaces the dist-info directory anderases the marker". pip does not replace the directory — it unlinks the paths named in
RECORDand removes the directory only if nothing else is left in it. An unrecorded extra file makes that
removal a no-op, which is the whole defect: the marker is designed to be erased by an upgrade and
is instead what survives one.
The marker was chosen to be
dist-info-adjacent precisely so it could not outlive the install itdescribes (
venv_provenance.py:21-25). On this evidence it outlives it in the one case thatmatters.
Acceptance criteria
dist-infoto alocation keyed by package+version that the reconciler owns end to end, or the writer records
it in
RECORDso pip removes it with the rest of the distribution.dist-infodirectory with noMETADATA/RECORD— before installing, so the 12 already on disk are recovered rather thanneeding 12 manual
--ignore-installedruns. Per#17038, removal of stored state isproposed and approved, never silently performed; a husk is tool-authored and carries the
tool's own marker, so state its classification explicitly in the implementation.
dist-infodirectory remains — the sequence that produced this, which the
#15067tests do not cover.has_tool_provenanceis not fooled by a husk: a husk currently answers "ours" for adistribution that is not installed.
Related
#15067— the feature that introduced the marker (CLOSED)#15063— the builtin update path cannot remove a dependency, the problem#15067addressed#17331— the other failure in the same run, on the non-manager node