EnvCause
Git bisect finds the bad commit. EnvCause finds the bad configuration.
EnvCause compares a known-good .env file with a known-bad one, repeatedly runs your reproduction command, and uses delta debugging to reduce all changed variables to a 1-minimal failure-inducing set.
It is deliberately local and dependency-free: your environment values are not sent anywhere.
Install
python -m pip install envcause
Example
envcause \
--good examples/good.env \
--bad examples/bad.env \
-- python examples/demo_app.py
Example output:
Original differing variables : 8
Failure-inducing variables : 2
1-minimal failure-inducing change set:
FEATURE_NEW_AUTH: false -> true
JWT_ALGORITHM: HS256 -> RS256
Why this is useful
Configuration failures often come from many changes landing together: feature flags, URLs, credentials, timeouts, pool sizes, provider choices, or deployment-specific switches. Testing them manually is slow, and checking one variable at a time misses failures caused by combinations.
EnvCause searches combinations automatically.
Usage
envcause --good GOOD.env --bad BAD.env [options] -- COMMAND [ARGS...]
By default, a non-zero process exit code means the failure reproduced.
Match a specific error instead
envcause \
--good .env.local \
--bad .env.staging \
--contains "Connection refused" \
-- npm test
This is useful when the command can fail for unrelated reasons.
For patterns that vary between runs, use a Python regular expression:
envcause --good good.env --bad bad.env --matches 'HTTP (500|503)' -- pytest -q
--contains and --matches search the combined stdout and stderr.
Match failures from JUnit XML
envcause \
--good good.env \
--bad bad.env \
--junit test-results.xml \
-- pytest --junitxml=test-results.xml
A candidate fails when the report contains a <failure> or <error> element. The command should overwrite the report on every run. Relative report paths are resolved from --cwd when supplied.
Reduce flaky failures
envcause --good good.env --bad bad.env --repeat 3 -- pytest -q
A candidate counts as failing only if it reproduces on every repeat.
Write a small reproduction file
envcause \
--good good.env \
--bad bad.env \
--write-repro minimal.env \
-- pytest -q
The generated file contains the actual bad-state values. Terminal output redacts values whose variable names look secret-sensitive unless --show-values is supplied.
Save a machine-readable report
envcause --good good.env --bad bad.env --report-json result.json -- pytest -q
The JSON report includes the command, matching mode, run and cache counts, and the reduced changes. Secret-looking values remain redacted unless --show-values is supplied.
Candidate caching
EnvCause caches candidate results in memory during each reduction, avoiding duplicate command executions when the delta-debugging search revisits a change set. Use --no-cache when the reproduction command is stateful and every candidate must be rerun.
To reuse results across invocations, provide a cache file:
envcause --good good.env --bad bad.env --cache-file .envcause-cache.json -- pytest -q
The cache stores SHA-256 fingerprints and pass/fail outcomes, not raw environment values. Fingerprints include the relevant execution environment, command, matcher, working directory, timeout, and repeat count. Volatile GitHub runner bookkeeping such as per-step output paths and run counters is ignored. Known-good and known-bad configurations are always verified with fresh runs before cached candidates are used.
Follow long reductions
envcause --good good.env --bad bad.env --progress -- pytest -q
Progress is written to stderr and shows the candidate number, number of changed variables, command-run count, and whether the result came from cache.
GitHub Actions
EnvCause can run directly in a workflow as a composite action:
jobs:
diagnose-config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Restore EnvCause candidate cache
uses: actions/cache@v5
with:
path: .envcause-cache.json
key: envcause-${{ runner.os }}-${{ github.ref_name }}
- name: Reduce the failing configuration
id: envcause
uses: deeneshchowdhary/EnvCause@v1
with:
good: config/good.env
bad: config/bad.env
command: pytest -q
matches: 'Connection refused|HTTP 503'
- name: Upload the redacted report
uses: actions/upload-artifact@v7
with:
name: envcause-report
path: ${{ steps.envcause.outputs.report-path }}
The action installs no project dependencies of its own and executes the command without a shell. The command input supports shell-style quoting for arguments, but shell operators such as pipes and redirects are not interpreted.
By default it:
- writes
envcause-report.jsonwith secret-looking values redacted - uses
.envcause-cache.jsonfor candidate caching - shows reduction progress in the action log
- adds a result table to the GitHub job summary
Available outputs are report-path, repro-path, failure-inducing-count, command-executions, and cache-hits. Set write-repro to create a minimal .env file; unlike the default JSON report, that file contains the real bad-state values and should be handled as a secret-bearing artifact. Set show-values: "true" only when exposing configuration values in logs and summaries is acceptable.
The repository's own CI workflow exercises the action locally on every push and pull request.
How the configuration model works
EnvCause starts from the good file as the baseline. Each differing variable can then be switched independently into its state from the bad file.
This also handles variables that exist in only one file:
- present only in
bad.env→ candidate change sets the variable - present only in
good.env→ candidate change unsets the variable
Variables inherited from the parent shell remain available unless overridden by the supplied files.
Important limitation: 1-minimal is not globally smallest
EnvCause uses the classic ddmin delta-debugging strategy. The result is 1-minimal: removing any one remaining change stops reproducing the failure. There may theoretically be another unrelated failure-inducing set with fewer variables.
That tradeoff keeps the number of command executions practical.
Safety
.env files commonly contain secrets. EnvCause:
- runs locally
- has no telemetry or network code
- redacts values for names containing terms such as
SECRET,TOKEN,PASSWORD,KEY, orAUTH - shows variable names by default because names themselves can still be sensitive in some organizations
Use --show-values only when appropriate.
MVP roadmap
Potential next steps:
- JSON / YAML / TOML config reduction
- parallel candidate execution
- Docker / Kubernetes environment adapters
envcause explainreports- multiple known-good / known-bad runs for nondeterministic systems
Development
python -m unittest discover -s tests -v
No runtime dependencies are required.
Contributions are welcome. See CONTRIBUTING.md for setup and pull-request guidance.
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 envcause-0.1.0.tar.gz.
File metadata
- Download URL: envcause-0.1.0.tar.gz
- Upload date:
- Size: 19.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d29ecec092438b0467229780ae976d8e2da65686ae25efdfde4d6af05bc151b7
|
|
| MD5 |
f0ade1e7648806e1b97ec60229c2cef9
|
|
| BLAKE2b-256 |
1514ca9385c8ac5d91c580797c47d1e0ac0ea881fa76b30837ec4183fa71e880
|
Provenance
The following attestation bundles were made for envcause-0.1.0.tar.gz:
Publisher:
publish.yml on deeneshchowdhary/EnvCause
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envcause-0.1.0.tar.gz -
Subject digest:
d29ecec092438b0467229780ae976d8e2da65686ae25efdfde4d6af05bc151b7 - Sigstore transparency entry: 2387359170
- Sigstore integration time:
-
Permalink:
deeneshchowdhary/EnvCause@23dc2c4db2942c636d3baba6270d4a878107676c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/deeneshchowdhary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@23dc2c4db2942c636d3baba6270d4a878107676c -
Trigger Event:
release
-
Statement type:
File details
Details for the file envcause-0.1.0-py3-none-any.whl.
File metadata
- Download URL: envcause-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c75f8b525b9bddedb288e2cb4c7c8869a0c4ed9dd64d7937fbf333fb0ddefd2
|
|
| MD5 |
46c0566d446e32ccd4126e8d90728890
|
|
| BLAKE2b-256 |
2d6f3535674f92332a361cb7a69f9c8a38407414cd6ed2d6815a777e4342ffe3
|
Provenance
The following attestation bundles were made for envcause-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on deeneshchowdhary/EnvCause
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envcause-0.1.0-py3-none-any.whl -
Subject digest:
9c75f8b525b9bddedb288e2cb4c7c8869a0c4ed9dd64d7937fbf333fb0ddefd2 - Sigstore transparency entry: 2387359298
- Sigstore integration time:
-
Permalink:
deeneshchowdhary/EnvCause@23dc2c4db2942c636d3baba6270d4a878107676c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/deeneshchowdhary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@23dc2c4db2942c636d3baba6270d4a878107676c -
Trigger Event:
release
-
Statement type: