Root-cause diagnosis for 'passes locally, fails in CI' — ranks the likely cause of environment drift and prints a plain-English fix.
Project description
EnvFixer
Root-cause diagnosis for "passes locally, fails in CI."
EnvFixer captures a privacy-safe fingerprint of a Python environment (interpreter, OS/arch/libc, installed packages, and environment-variable names), compares two of them, and ranks the most likely cause of an environment-drift failure — then prints a plain-English fix for each.
Every other tool in this space stops at "here's the diff." EnvFixer tells you which difference is probably the cause, and how to fix it.
$ envfixer diff local.json ci.json
EnvFixer — diagnosis
=====================
3 candidate cause(s), most likely first:
1. [HIGH ] 82/100 ████████████████···· package_missing: pydantic
working: '2.4.2' failing: None
why: A package present where the code works is entirely absent where it
fails; a missing import is the single most common CI failure.
fix: Install 'pydantic' in the failing environment — add
'pydantic==2.4.2' to your requirements/lockfile so CI installs it.
2. [MEDIUM] 58/100 ████████████········ python_version: python
working: '3.11.5' failing: '3.9.18'
...
- Zero runtime dependencies. The core is pure standard library, so it runs inside any minimal CI container.
- Privacy-safe by construction. It records environment-variable names only — never values. The fingerprint is plain JSON you can eyeball.
- A CI guardrail, not just a report. Exit codes and
--fail-onlet it gate a build when environment drift is the likely culprit.
Install
pip install envfixer # once published to PyPI
Quick start
Capture a fingerprint inside the environment you want to snapshot (run it in
the target virtualenv, e.g. python -m envfixer capture, so you fingerprint
the right interpreter):
# on your machine
python -m envfixer capture -o local.json
# in CI
python -m envfixer capture -o ci.json
Diagnose the difference (first argument = where it works, second = where it fails):
envfixer diff local.json ci.json
Have the failing traceback? Pass it — differences it names are boosted to near-certainty:
envfixer diff local.json ci.json --traceback ci-error.log
Health-check a single environment (no second fingerprint needed):
envfixer check
Commands
| Command | What it does |
|---|---|
capture [-o FILE] |
Write this environment's fingerprint (stdout by default). |
diff WORKING FAILING [--traceback FILE] |
Rank the likely causes of the failure. |
check [FINGERPRINT] |
Surface broken dependency chains in one environment. |
Shared reporting flags on diff/check:
--format {human,json,markdown,sarif}—markdownfor PR comments,sariffor inline GitHub code-scanning annotations.--fail-on {none,low,medium,high}— exit non-zero when a cause of at least this confidence is found (defaulthigh).--top N— show only the top N results.
Use in CI (GitHub Actions)
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install -e . && pip install envfixer
- run: python -m envfixer capture -o ci.json
# compare against a committed baseline captured locally
- run: envfixer diff local.json ci.json --format markdown --fail-on high
A reusable composite action lives in .github/actions/envfixer.
How the ranking works
The ranking is a transparent rule system, not a black box — every score comes with a human-readable rationale. In brief:
- Missing package (present where it works, absent where it fails) scores highest — it's the most common real cause.
- Version mismatches are scored by blast radius (major > minor > patch).
- Python / libc / arch differences are scored by how often they actually break code (e.g. glibc-vs-musl is weighted heavily).
- Env-var names are filtered by significance:
DATABASE_URL,*_TOKEN,AWS_*,PYTHONPATHscore high; shell noise likeSHLVL/TERMis dropped. - A provided traceback strongly boosts any difference it names.
See envfixer/rank.py for the exact rules.
Evaluation
Because "ranks by likely cause" is an empirical claim, EnvFixer ships a
benchmark that measures it against both a synthetic fault injector and a curated
real-world corpus (missing test plugins, urllib3 v2, Python 3.12 dropping
distutils, Alpine/musl wheel gaps, NumPy 2.0 ABI breaks, …). On the current
corpus (18 labelled cases): Top-1 100%, MRR 1.000, 0 false positives. See
EVALUATION.md.
python -m evaluation.benchmark
Privacy
EnvFixer never reads environment-variable values, only their names. The fingerprint is human-readable JSON; inspect it before sharing.
Contributing
Issues and PRs welcome — see CONTRIBUTING.md. Good first
contributions: new fault scenarios in evaluation/generate.py and new remedies
in envfixer/fixes.py.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file envfixer-0.1.0.tar.gz.
File metadata
- Download URL: envfixer-0.1.0.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51c68a313ff913c1db6c8912f7882e11076f3cb373a42bbc6ffa1bfcb4e6f4f5
|
|
| MD5 |
0e7af9c70404558c6ea9d74f194f30bb
|
|
| BLAKE2b-256 |
d5aad0e76595aa0fecaf6951f863fdfe18cf180e87813e035e897f0d96c2d809
|
Provenance
The following attestation bundles were made for envfixer-0.1.0.tar.gz:
Publisher:
release.yml on hemanthrayuduu/envdoctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envfixer-0.1.0.tar.gz -
Subject digest:
51c68a313ff913c1db6c8912f7882e11076f3cb373a42bbc6ffa1bfcb4e6f4f5 - Sigstore transparency entry: 2266171000
- Sigstore integration time:
-
Permalink:
hemanthrayuduu/envdoctor@365be87e032b7c9282aea06636bc4c1df810a0ce -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hemanthrayuduu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@365be87e032b7c9282aea06636bc4c1df810a0ce -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file envfixer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: envfixer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffd31f5dd11982c2ea36d6492b506a79709b069b9247fd0f8eb45dca5733ba46
|
|
| MD5 |
3eabefaa266efc7b89cb1ee03bbc0dcc
|
|
| BLAKE2b-256 |
772ad3b325b0fbb93b98015e429dc17d35471dc6a60ff620246ce44302da3280
|
Provenance
The following attestation bundles were made for envfixer-0.1.0-py3-none-any.whl:
Publisher:
release.yml on hemanthrayuduu/envdoctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envfixer-0.1.0-py3-none-any.whl -
Subject digest:
ffd31f5dd11982c2ea36d6492b506a79709b069b9247fd0f8eb45dca5733ba46 - Sigstore transparency entry: 2266171057
- Sigstore integration time:
-
Permalink:
hemanthrayuduu/envdoctor@365be87e032b7c9282aea06636bc4c1df810a0ce -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hemanthrayuduu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@365be87e032b7c9282aea06636bc4c1df810a0ce -
Trigger Event:
workflow_dispatch
-
Statement type: