greenwash
Find required CI checks that can go green without doing the work.
A green required check is treated as proof the tests ran. It is not. From GitHub's own documentation:
Required status checks must have a
successful,skipped, orneutralstatus before collaborators can make changes to a protected branch.
Skipped counts as passed. So a job gated behind an if: that evaluated false, a
job skipped because the job it needed failed, a workflow filtered out by paths:
and backstopped by a same-named always-green twin, or a step wrapped in
continue-on-error: true all produce the same green tick as a full passing run.
Nothing in the UI tells them apart. The merge succeeds, main breaks, and the
pull request still shows all-green months later.
greenwash reads your branch rules and your workflow definitions and tells you which of your gates are real.
Installation
pip install greenwash
Requires Python 3.10+ and a GitHub token, from GITHUB_TOKEN, GH_TOKEN, or
gh auth login.
Quick start
greenwash audit # the repository you are standing in
greenwash audit --repo owner/name
$ greenwash audit --repo home-assistant/core
home-assistant/core (dev) 8 required checks, 13 workflows, via ruleset
2 hollow, 5 blocked, 2 advisory
[hollow] Check all requirements (skips-with-upstream)
where .github/workflows/ci.yaml:gen-requirements-all
detail needs: info, base
why if an upstream job fails or is skipped this job is skipped too,
which satisfies the gate rather than failing it -- the failure
merges
Three severities:
- hollow — the check can report success without the work running.
- blocked — the check can never report at all, so the gate is stale or the branch is unmergeable.
- advisory — the signal is weakened but not void, or the finding depends on a choice you may have made deliberately.
What it looks for
| Finding | Severity | What it means |
|---|---|---|
skippable-condition |
hollow | The job's if: can be false, and a skip satisfies the gate |
skips-with-upstream |
hollow | An unguarded needs: — when the upstream fails, this job skips and the failure merges |
aggregate-gate-without-check |
hollow | A job running under always() that never inspects needs.*.result, so it passes when everything it gates on failed |
continue-on-error-job |
hollow | The job reports success even when its steps fail |
duplicate-check-name |
hollow | Two or more jobs that can fire on a pull request report under the same name |
required-check-has-no-job |
blocked | Nothing in any workflow reports under this name |
aggregate-gate-allows-skips |
advisory | alls-green with allowed-skips, so a green tick does not prove those jobs ran |
continue-on-error-step |
advisory | A step can fail without failing the check |
escape-hatch-step |
advisory | || true or set +e discards a command's failure inside the shell |
path-filtered-workflow |
advisory | A paths: filter means the workflow may not run for some pull requests |
In CI
greenwash audit --fail-on hollow
Exit 1 when any hollow finding exists, so a repository can gate on its own gates
being real. --fail-on any includes advisories. --json gives the same data
machine-readable.
What it does not do
It cannot tell you whether a job that ran did any real work. A job whose
pytest step collected zero tests looks identical to one that ran the suite.
This is a static audit of workflow definitions against branch rules; proving the
work happened needs run history and a declared witness, which is not built yet.
It cannot read what your token cannot read. Classic branch protection needs
admin. When a branch reports protected: true and no rules come back, greenwash
says so and stops rather than reporting a clean bill of health — a tool that
called an invisible gate a missing one would be committing the error it exists
to find.
It does not resolve reusable workflows. A uses: workflow reports as
caller / job, which this version does not follow, so those may show up as
required-check-has-no-job. Third-party apps that post check runs will too.
Merge queues change when the gate applies. A job gated on merge_group is
skipped on the pull request and runs in the queue. greenwash reports the
definition; whether that is a problem depends on where you enforce merging.
Why the name
Greenwashing is presenting something as better than it is on the strength of a symbol rather than the substance behind it. A green tick that certifies nothing is the same trick.
License
MIT
Metadata
Release files for greenwash 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| greenwash-0.1.0.tar.gz | 55.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| greenwash-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.1 kB
Release files / greenwash-0.1.0.tar.gz
| Download URL | greenwash-0.1.0.tar.gz |
|---|---|
| Size | 55.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
367900a8ceb6d3eb59835d758c80c16410d08463416f4e78adc2b501c0052716
|
|
BLAKE2b-256 checksum How to use checksums |
239b6d14af226b3e4ceb382e01fb42651497a1d1c6f1fa835eebabee8bbe9f77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.
Transparency logRelease files / greenwash-0.1.0-py3-none-any.whl
| Download URL | greenwash-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d41160d60728889ff39239f08a50938fde90bc18f0b0e25ef238cfaf53ff3e4
|
|
BLAKE2b-256 checksum How to use checksums |
2967db6ee294dcdbabb48724f031bf6aadb376b1c0687ba9f12e6d3b86cd8a6e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.
Transparency log