gha-doctor — psf/requests F · 27/100
generated 2026-08-01 01:34 UTC · gha-doctor 0.32.0
Workflow checkup (8 files)
| rule | severity | location | message |
|---|---|---|---|
| D002 | warning | .github/workflows/close-issues.yml:11 | job close_qa has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D002 | warning | .github/workflows/close-issues.yml:25 | job close_feature_request has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D001 | warning | .github/workflows/codeql-analysis.yml:9 | pull_request workflow has no concurrency group: pushing new commits leaves superseded runs burning minutes |
| D014 | info | .github/workflows/codeql-analysis.yml:15 | cron 0 23 * * 0 fires at minute 0 — the peak-load window where GitHub delays and sometimes skips scheduled runs |
| D002 | warning | .github/workflows/codeql-analysis.yml:21 | job analyze has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D001 | warning | .github/workflows/lint.yml:3 | pull_request workflow has no concurrency group: pushing new commits leaves superseded runs burning minutes |
| D013 | warning | .github/workflows/lint.yml:3 | triggers on both push (all branches) and pull_request: every commit to a PR from this repo runs CI twice |
| D003 | warning | .github/workflows/lint.yml:18 | actions/setup-python without cache: input re-downloads dependencies on every run |
| D014 | info | .github/workflows/lock-issues.yml:5 | cron 0 0 * * * fires at minute 0 — the peak-load window where GitHub delays and sometimes skips scheduled runs |
| D002 | warning | .github/workflows/lock-issues.yml:12 | job action has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D002 | warning | .github/workflows/publish.yml:18 | job build has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D003 | warning | .github/workflows/publish.yml:31 | actions/setup-python without cache: input re-downloads dependencies on every run |
| D002 | warning | .github/workflows/publish.yml:52 | job publish has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D002 | warning | .github/workflows/publish.yml:74 | job publish-test-pypi has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D001 | warning | .github/workflows/run-tests.yml:3 | pull_request workflow has no concurrency group: pushing new commits leaves superseded runs burning minutes |
| D013 | warning | .github/workflows/run-tests.yml:3 | triggers on both push (all branches) and pull_request: every commit to a PR from this repo runs CI twice |
| D011 | warning | .github/workflows/run-tests.yml:15 | job build matrix expands to 24 jobs per trigger |
| D002 | warning | .github/workflows/run-tests.yml:41 | job no_chardet has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D003 | warning | .github/workflows/run-tests.yml:52 | actions/setup-python without cache: input re-downloads dependencies on every run |
| D002 | warning | .github/workflows/run-tests.yml:63 | job urllib3 has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
| D003 | warning | .github/workflows/run-tests.yml:74 | actions/setup-python without cache: input re-downloads dependencies on every run |
| D001 | warning | .github/workflows/typecheck.yml:3 | pull_request workflow has no concurrency group: pushing new commits leaves superseded runs burning minutes |
| D013 | warning | .github/workflows/typecheck.yml:3 | triggers on both push (all branches) and pull_request: every commit to a PR from this repo runs CI twice |
| D003 | warning | .github/workflows/typecheck.yml:22 | actions/setup-python without cache: input re-downloads dependencies on every run |
| D001 | warning | .github/workflows/zizmor.yml:5 | pull_request workflow has no concurrency group: pushing new commits leaves superseded runs burning minutes |
| D002 | warning | .github/workflows/zizmor.yml:13 | job zizmor has no timeout-minutes (default is 360): one hung step can burn 6 hours of runner time |
Run history: psf/requests (last 120 runs)
| workflow | runs | success | p50 | p95 |
|---|---|---|---|---|
| Lock Threads | 23 | 0% | 0.1m | 0.1m |
| Type Check | 20 | 100% | 0.4m | 0.5m |
| Tests | 19 | 92% | 4.6m | 4.9m |
| Lint code | 19 | 100% | 0.4m | 0.5m |
| CodeQL | 18 | 100% | 1.1m | 1.2m |
| GitHub Actions Security Analysis with zizmor 🌈 | 15 | 100% | 0.3m | 0.5m |
| pre_commit in /. - Update #1468561744 | 1 | 100% | 0.8m | 0.8m |
| github_actions in /. - Update #1468556551 | 1 | 100% | 4.6m | 4.6m |
| pre_commit in /. - Update #1487428555 | 1 | 100% | 0.9m | 0.9m |
| github_actions in /. - Update #1487419578 | 1 | 100% | 1.1m | 1.1m |
| pre_commit in /. - Update #1459026798 | 1 | 100% | 1.0m | 1.0m |
| github_actions in /. - Update #1459021737 | 1 | 100% | 4.1m | 4.1m |
Flaky jobs (failed and passed on the same commit):
| job | workflow | flaky commits | wasted minutes |
|---|---|---|---|
| build (3.12, windows-latest) | Tests | 1 | 14 |
| build (3.14, windows-latest) | Tests | 1 | 4 |
| build (3.10, windows-latest) | Tests | 1 | 4 |
Flaky tests (seen failing in 4 of 4 flaky-failure logs; the same commit also passed):
| test | framework | fails | commits | job |
|---|---|---|---|---|
tests/test_requests.py::TestRequests::test_pyopenssl_redirect | pytest | 4 | 2 | build |
Matrix balance (a matrix finishes when its slowest shard does; billable minutes unchanged — this is PR feedback latency):
| job | workflow | shards | wall p50 | even-split p50 | waiting on straggler |
|---|---|---|---|---|---|
| build | Tests | 23 | 4.5m | 2.6m | 1.9m |
Worst: build — slowest shard (3.15-dev, windows-latest) 4.5m vs fastest (3.12, ubuntu-22.04) 1.6m (median of 12 runs).
Wasted compute: 1055 of 4490 minutes (failed runs 9 + retries 1045).
Estimated cost (GitHub-hosted rates; free for public repos on standard runners): $44.93 for the sample (5616 billable min), of which $10.60 went to failures/retries and $9.01 to per-job minute round-up.
Cache: 0 caches, 0 MB (0% of the 10 GB limit); 0 MB stale (unused 7+ days), 0 MB pinned to PR refs.
Artifacts: 18 total; 0 MB not yet expired in the 18 most recent. Top producer: dist (10 uploads, 2 MB, kept 5d).
Cache hit rate (12 sampled job logs): 100% — 1 restores: 1 hits, 0 partial, 0 misses.
Top wins
- Cut failures and retries — ~$14.18/mo — 9 failed-run min + 1045 retried-attempt min bought nothing ($10.60 of the sampled spend); worst flake: "build (3.12, windows-latest)" in "Tests"; most-seen flaky test: "tests/test_requests.py::TestRequests::test_pyopenssl_redirect" (pytest, failed in 4 sampled logs)
- Consolidate tiny jobs — ~$12.05/mo — per-job round-up billed 1126 min the jobs never used (20% of spend) — merge short jobs or shrink the matrix
- Stop double-running PR pushes — 3 workflows trigger on both unscoped push and pull_request — every PR commit runs twice; scope push to your default branch (see: gha-doctor --explain D013)
- Cache dependencies — 5 setup steps download the same dependencies every run (gha-doctor --fix handles this (D003))
- Cancel superseded runs — 5 workflows have no concurrency group — runs for already-replaced commits keep burning minutes (gha-doctor --fix handles this (D001))
dollar wins projected to 30 days from the 22.4-day run sample; private-repo rates, public repos run free.
Health score: F (27/100)
Basis: static checks + run history.
| Component | Deducted | Max | Detail |
|---|---|---|---|
| workflow hygiene | −30 | 30 | 24 warning(s), 2 info finding(s) across 8 file(s) |
| success rate | −16.7 | 25 | 73% of 90 decisive runs succeeded (skipped/cancelled not counted) |
| queue time | −0.1 | 5 | average 3 s waiting for a runner |
| flakiness | −15 | 15 | 3 jobs failed AND passed on the same commit |
| wasted minutes | −11.7 | 15 | 23% of sampled compute minutes went to failed runs or retries |
| cache hit rate | −0 | 10 | 100% of 1 sampled restores hit |