Skip to main content

python-env-repair

Detect and repair console_scripts launchers of a Python environment after the environment has been moved or copied, without reinstalling, downloading, or modifying any package.

Package metadata is the authority: only entry points declared in the environment's own *.dist-info / *.egg-info are considered, and an existing file is replaced only when it is a recognized wrapper for that same entry point. Standalone binaries in the scripts directory (uv, ruff, git, node, ...) are never modified or executed.

Design documents: implementation plan, repair design, logging design, Ruff/ty configuration, compatibility note, implementation status.

Deployment

The utility operates on the environment of the interpreter that runs it, and it never installs anything itself. It has no runtime dependencies: the script generator is built in, and the Windows console launcher stubs are shipped inside the package (copied unmodified from distlib 0.4.3, PSF-2.0; see python_env_repair/_launchers/). Ways to provide it:

  • install the wheel into the target environment ahead of time, or
  • put python_env_repair/ (a directory or a zip) on PYTHONPATH for the repair run.

Usage

Always call the target interpreter by its absolute path, not a console script that may itself be broken:

/path/to/venv/bin/python -B -m python_env_repair detect
/path/to/venv/bin/python -B -m python_env_repair repair --dry-run --json > plan.json
/path/to/venv/bin/python -B -m python_env_repair repair
/path/to/venv/bin/python -B -m python_env_repair repair --verbose
/path/to/venv/bin/python -B -m python_env_repair repair --log-dir ./repair-logs
/path/to/venv/bin/python -B -m python_env_repair verify --json

-B stops the utility's own imports from writing bytecode. It does not sandbox package code; only explicitly configured startup checks run package code.

On Windows, after moving or copying an environment, use --relocated (see Windows launchers):

C:\path\to\venv\Scripts\python.exe -B -m python_env_repair repair --dry-run --relocated
C:\path\to\venv\Scripts\python.exe -B -m python_env_repair repair --relocated

Output

  • stderr: phase-tagged progress (--verbose adds evidence and tracebacks, --quiet shows warnings and errors only).
  • Color: logs and the text summary are colored by default, including when redirected. NO_COLOR (any non-empty value) or TERM=dumb turns it off. --color auto colors only terminals, --color always ignores NO_COLOR, and --color never disables it. JSON output and diagnostic files are never colored. On Windows, ANSI support is switched on for the console first; legacy consoles that cannot enable it get plain text.
  • stdout: the final result as a text summary, or with --json exactly one JSON report object.
  • Files: none by default. --log-dir PATH alone enables events.jsonl (DEBUG events), manifest.json (plan saved before the first modification) and report.json in a new PATH/<UTC timestamp>-<id>/ directory. --dry-run --log-dir is rejected, because a dry run writes nothing.

Verification

Default verification is structural: generated bytes, interpreter binding (Unix), execute permission, and the replacement outcome. The output then says "structural verification passed; startup not checked". To also run a CLI, approve a command for each entry explicitly:

python -B -m python_env_repair repair --verification execute --verify-command "mytool=--version" --verify-timeout 20

Commands run by absolute artifact path, without a shell, with no stdin, a timeout, and at most 64 KiB kept per output stream. An entry without an approved command is reported as unverified. There is no automatic fallback to --help, --version, or importing the entry point.

If a required check fails after writing, the original file is restored from the recovery copy, and both the write and the rollback are reported. If the rollback itself fails, the recovery copy is kept and its path is printed.

Windows launchers and relocation evidence

The interpreter path inside a Windows .exe launcher is not parsed. An existing, owned launcher is unknown unless explicit evidence is given. Without evidence, repair on Windows only creates missing launchers and leaves every existing one unchanged, even when the environment was moved.

Recommended on Windows: whenever the environment has been moved or copied, run repair with --relocated. Missing launchers are created without the flag, but existing ones are rebuilt only with it. Launchers are regenerated only when metadata declares them and the existing file is a recognized launcher for that same entry point, so standalone binaries and unrecognized files stay untouched. Run repair --dry-run --relocated first to see the list.

  • --relocated: the caller asserts the environment was moved; owned launchers are rebuilt. On Linux and macOS it changes nothing, because shebangs are inspected directly.
  • --previous-manifest PATH: a manifest.json or report.json from an earlier run (or {"python": ..., "console_scripts": [...]}). A different recorded Python means rebuild. The same Python plus an unchanged hash means valid, and only hashes of entries that run reported as repaired or valid count. Paths in the file are never used as write targets.

Nothing is saved automatically to help later runs. Keep report.json (from a --log-dir run) if you want future evidence.

Exit codes

Code Meaning
0 Command completed. Finding candidates in detect/--dry-run is not a failure; read the report.
1 A repair or required verification failed or did not complete (verify also fails on unverified entries).
2 Invalid or conflicting arguments.
3 Run-level failure: environment not identifiable, environment busy (another repair holds the lock), requested diagnostics could not be written.
130 Interrupted.

Priority when several apply: 130, then 3, then 1.

Library use

from python_env_repair import Operation, RunOptions, run

report = run(RunOptions(operation=Operation.DETECT))
print(report.exit_code, [(r.script.name, r.overall_status.value) for r in report.results])

run() does not configure logging. Events go to the python_env_repair.* loggers, which have only a NullHandler unless the host attaches one.

Known limitations

  • Launcher repair is not full environment relocation. pyvenv.cfg, .pth files, .egg-link, package configuration, and native dependencies are out of scope. Python documents venvs as non-portable.
  • Regenerated scripts are not written back to RECORD, so their recorded hash can go stale. Validity never depends on the recorded hash.
  • Unsupported wrapper forms (for example #!/usr/bin/env python, relocatable $(dirname ...) wrappers, legacy Windows -script.py sidecar layouts) are reported as unknown and left unchanged.
  • pip/setuptools installer aliases (pip, pipX, pipX.Y, easy_install-X.Y) follow pip's naming rules. Missing aliases are not created, because installers may omit them on purpose.
  • Repairs run one at a time. Replacing several files is not one atomic transaction, and recovery after a machine crash is not provided.
  • The lock only coordinates instances of this utility, not package installers or other Python processes. Running processes are never terminated.
  • Native validation has been run on Linux only; see implementation status.

License

GPL-3.0-only; see LICENSE. The Windows launcher stubs in python_env_repair/_launchers/ are copied from distlib and keep their PSF-2.0 license (LICENSE.txt).

Development

python -m ruff check python_env_repair tests
python -m ruff format --check python_env_repair tests
python -m ty check --python <dev-python> python_env_repair tests
python -B -m pytest tests

Install with pip install -e . --group dev (pip 25.1 or newer). Development tools are listed without version constraints in [dependency-groups] in pyproject.toml, so the latest releases are installed. distlib is a test-only dependency: tests/test_generator.py checks that the built-in generator's output is byte-for-byte identical to distlib's, and is skipped when distlib is not installed. The check that the vendored stubs equal distlib's files runs only against distlib 0.4.3, the stubs' source release.

Metadata

Release files for python-env-repair 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-env-repair 0.1.0
File Size Uploaded Provenance Actions
python_env_repair-0.1.0.tar.gz 270.7 kB Without provenance Details for python_env_repair-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for python-env-repair 0.1.0
File Interpreter ABI Platform Actions
python_env_repair-0.1.0-py3-none-any.whl Python 3 none any Details for python_env_repair-0.1.0-py3-none-any.whl

Total release size: 534.5 kB

Release files / python_env_repair-0.1.0.tar.gz

Download URL python_env_repair-0.1.0.tar.gz
Size 270.7 kB
Tags Source
SHA-256 checksum
How to use checksums
31d552da8cc57ae35bc9cc661dc51e419f5444b7a33eae1013644117bed255c0
BLAKE2b-256 checksum
How to use checksums
3f274db2e78ad7580b5946cddd23872e0c1bcc4ac96aedddcfc6413b4255827d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / python_env_repair-0.1.0-py3-none-any.whl

Download URL python_env_repair-0.1.0-py3-none-any.whl
Size 263.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ec94ccef742f1365a3c734d5ef560222f7cb6051f159bb67e7bc225030d6f272
BLAKE2b-256 checksum
How to use checksums
f33eb34a1a64483ad389b5c4f42ae9ba1b7f706b68e3473886eaff7cc55ac77e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Security for python-env-repair 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page