Repository navigation
Reset the embed update log when it cannot be read - #3376
Merged
gaborbernat merged 3 commits intoOct 2, 2026
Merged
Conversation
The app data folder is shared between runs and outlives a single one, so the embed update log on disk may have been written by another version or left incomplete by an interrupted run. UpdateLog.from_app_data() passed whatever it read straight to from_dict(), which raised on a value of the wrong shape, and the seed then failed with "seed failed due to failing to download wheels pip", which names neither the cause nor a way out. Treat an unreadable log the way JSONStoreDisk.read() already treats unparseable JSON: drop it and carry on. from_app_data() now catches the shape errors, logs a warning naming the distribution, and returns an empty log, so the bundled wheel is used and the environment is still created. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
for more information, see https://pre-commit.ci
gaborbernat
force-pushed
the
gsz/reset-unreadable-embed-update-log
branch
from
October 2, 2026 15:48
e0c0cf4 to
56b8815
Compare
The fallback warned that it reset the log but left the file on disk, so each later run warned again and --no-periodic-update left it in place. Delete the file the way JSONStoreDisk.read() deletes invalid JSON, test against a real app data folder instead of a mocked read, assert the exact warning and that the file is gone, and name the fragment after this pull request.
gaborbernat
force-pushed
the
gsz/reset-unreadable-embed-update-log
branch
from
October 2, 2026 16:34
56b8815 to
7f74f0f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thanks for contributing, make sure you address all the checklists (for details on how see development documentation)
tox -e fix)docs/changelogfolderpyvenv.cfg, wheel downloads, app-data or release workflows: explained in thePR text which outside input the change handles, such as a path, a prompt or a downloaded file, and how it stops that
input from running as code. See the
security scope.
The bug
The embed update log lives at
<app-data>/wheel/<major.minor>/embed/3/<distribution>.jsonand records which newerseed wheels are known.
UpdateLog.from_app_data()insrc/virtualenv/seed/wheels/periodic_update.pypassed whatever itread straight into
from_dict(), which calls.get()on the result. A value of the wrong shape raises, and the seedfails with a message that names neither the cause nor a way forward:
Five shapes all do it, verified by calling
periodic_update()directly:End to end,
virtualenv <dest> --no-periodic-updateexits 1 for each of those and does not create the environment.This matters because the app data folder is shared and persists between runs. A log written by a different virtualenv
version, or left half written by an interrupted one, is enough to make every later seed fail until the user works out
that they need to delete a cache file.
The change
from_app_data()catches the shape errors, logs a warning naming the distribution, removes the file and returns anempty log. The bundled wheel is then used and the environment is created, and the next run starts without the bad
log instead of warning again.
This matches what the surrounding code already does:
JSONStoreDisk.read()deletes the file and returnsNonewhenthe JSON does not parse, so a corrupt log was already survivable, only a log that parses into the wrong shape was not.
Tests
Five cases, all failing on unmodified
main(288f13c) and passing with the fix.Before, with
src/virtualenv/seed/wheels/periodic_update.pyreverted tomain:After:
Each case writes the bad log into a temporary app data folder and asserts the bundled wheel is returned, the exact
warning is logged and the file is gone.
End to end, with a wrong-shape log planted in the app data folder,
virtualenv <dest> --no-periodic-updatenow exits 0for all four shapes I tried where it previously exited 1.
Wider selections, all passing:
Linter and type checks:
Full suite
The 11 failures above are unchanged by this branch. I compared the failing test IDs before and after by stashing the
change and re-running:
They are artifacts of this sandbox, all reproducible on unmodified
main:tests/unit/test_util.py(2): thesafe_deletetests rely on directory permissions denying an unlink, which doesnot happen for uid 0. This container runs as root.
tests/unit/activation/test_bash.py(9): bash renders the escaped\$prompt sequence as#for uid 0 and as$for any other user, so the expected prompt text does not match. Confirmed by running the same
PS1as root and asuid 65534: root gives
(a%n#HOME\b...), a normal user gives(a%n$HOME\b...).fish,cshandtcshare installed here, so those activator tests ran.powershellandnuare not installed, sothe PowerShell and Nushell activator tests skip. CI runs them, so a green local run does not guarantee a green CI run
for those files.
Security scope
The outside input is the embed update log file, which sits in the shared app data folder. It is data read with
json.loads, and nothing on this path executes or evaluates it. Before, a value of an unexpected shape propagated anexception out of the seed; now it is reported and discarded, which narrows what a bad file can do rather than widening
it. No new input is read, and the fallback only ever selects the bundled wheel, which is the same one used when no log
exists at all.