Skip to main content

toil-radar

Estimates how much time a team loses to toil — reverts, hotfixes, flaky CI, manual button-pushing — from signals already sitting in git history and GitHub Actions. Point it at a repo and get back estimated hours, a trend, and a ranked list of what to automate first.

The SRE handbook says to keep toil under 50% of engineering time. Almost nobody measures it, and grepping commit messages for "fix" doesn't work — it flags half of normal development. So toil-radar only counts events that don't happen unless someone was cleaning up a mess:

signal what happened
revert a revert or rollback commit
hotfix_merge a merge from a hotfix or emergency branch
quick_fix the same author patched the same file again within 2 hours, with a corrective message
ci_rerun a workflow run needed more than one attempt
manual_dispatch someone triggered a workflow by hand
broken_main the default branch went red and someone fixed it forward

The git signals work offline. The three GitHub Actions signals use the gh CLI if it's installed and authenticated, and are skipped quietly if not.

broken_main counts episodes, not failures. Consecutive failed runs of one workflow on the default branch collapse into a single event that ends at the next green run — three failed pushes in ten minutes is one person fixing one mistake, not three incidents. It's costed by how long the branch stayed red (floored at 10 minutes, capped at 2 hours), and an episode still red at scan time isn't counted until it recovers.

Each episode also records the commit its first failing run was built from, so summary can name what actually broke the branch rather than everything that happened to land nearby. Commits older than the scanned history just don't contribute.

Every other event gets a rough cost in minutes (weights are in toil_radar/git_signals.py, deliberately conservative). Anything that started on a night or weekend counts 1.5x. The total isn't meant to be payroll-accurate — it's a consistent number you can trend over time and use to decide what to automate first. Rescanning never double-counts: events are deduplicated by commit hash or run id.

Install

pip install toil-radar               # CLI, no dependencies
pip install "toil-radar[dashboard]"  # adds the Streamlit dashboard

Usage

toil-radar scan /path/to/repo        # add --no-github to skip the gh API
toil-radar scan . --days 60
toil-radar scan ~/code/*             # several at once
toil-radar summary
toil-radar summary --repo /path/to/repo --days 90
toil-radar export --output /var/lib/node_exporter/toil.prom
toil-dashboard

Scans accumulate in one database; summary aggregates across every repo you've scanned unless you filter with --repo. scan takes any number of paths, and a bad one doesn't abort the rest — anything in the list that isn't a git repo is reported and skipped, so globbing a directory of projects does the sensible thing. The exit code is non-zero if any path failed.

Prometheus

export writes the stored metrics in Prometheus text format, for trending toil over months rather than eyeballing a single window. Point --output at the node_exporter textfile collector's directory and run it on a schedule; the file is written and renamed into place, so a scrape never catches it half-written.

toil_radar_events{repo="/srv/app",signal="broken_main"} 3
toil_radar_seconds{repo="/srv/app",signal="broken_main"} 3084
toil_radar_night_weekend_events{repo="/srv/app"} 9
toil_radar_capacity_ratio 0.004
toil_radar_window_seconds 15552000

Values are in seconds because that's the Prometheus convention, even though the CLI talks in minutes and hours. CI runs promtool check metrics over the output on every push, so the format is verified by a real Prometheus parser rather than by eye.

Example

Real output from scanning this repo:

Toil Radar - last 180 days - all repos
============================================================
Estimated toil: 4.6h (~0% of one engineer's time)
Out-of-hours events: 9 (nights/weekends)

signal                        events  est. hours
------------------------------------------------
rapid follow-up fixes             10         3.4
broken default branch              3         0.9
CI re-runs                         1         0.3

Churn hotspots (same file touched in many commits):
  pyproject.toml  (11 commits)
  tests/test_basic.py  (8 commits)
  README.md  (6 commits)
  CHANGELOG.md  (6 commits)

What broke the default branch:
  tests/test_basic.py  (2 of 3 episodes)
  toil_radar/store.py  (1 of 3 episodes)
  toil_radar/__init__.py  (1 of 3 episodes)

Top automation candidates:
1. rapid follow-up fixes: 10 events, ~3.4h
   Fix-the-fix churn means feedback arrives too late - add the missing
   linter, type check, or test that would catch these pre-push.
   e.g. "Fix author date parsing on Python < 3.11 (trailing Z)" (2026-07-09)
2. broken default branch: 3 events, ~0.9h
   Breakage is landing on the default branch and being fixed forward under
   pressure - gate merges on the checks that failed, so the build breaks in
   a PR instead of on main.
   e.g. "CI red on main for 15 min (1 failed run)" (2026-07-09)
3. CI re-runs: 1 events, ~0.3h
   Re-run workflows usually mean flaky tests or flaky infra - quarantine
   the flakiest jobs and fix them; every re-run is babysitting.
   e.g. "Publish to PyPI re-run (attempt 3)" (2026-03-30)

The verdict on our own history was fair. The packaging churn really was toil, that PyPI publish really did take three attempts, and the red-main episodes were real scrambles rather than noise. The attribution earns its keep too: it fingers tests/test_basic.py in two of the three, and the July episode traces to the detection-engine rewrite, fixed twenty minutes later by a commit whose message is "Fix author date parsing on Python < 3.11".

Development

git clone https://github.com/amrutp24/toil-radar
cd toil-radar
pip install -e ".[dashboard,dev]"
pytest

Roadmap

  • deploy→fix→deploy loop detection
  • per-team and per-author views

License

MIT — see LICENSE

Metadata

Release files for toil-radar 0.5.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 toil-radar 0.5.0
File Size Uploaded
toil_radar-0.5.0.tar.gz 37.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for toil-radar 0.5.0
File Interpreter ABI Platform
toil_radar-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.0 kB

Release files / toil_radar-0.5.0.tar.gz

Download URL toil_radar-0.5.0.tar.gz
Size 37.9 kB
Tags Source
SHA-256 checksum
How to use checksums
91e0e6ddecc848e528adbabfd0d9451d7e21f155dab482fe72248034cd455d87
BLAKE2b-256 checksum
How to use checksums
a67025b1dfc132ccbc2ef691290b701e8e65d5405a66a8664da6a8686b109388
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / toil_radar-0.5.0-py3-none-any.whl

Download URL toil_radar-0.5.0-py3-none-any.whl
Size 25.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3dacbf95ab6abd1faae1f17157de605e1cd735e98dd9a50c66d3b289ee6aa2e8
BLAKE2b-256 checksum
How to use checksums
17decd48a83963d2151ec4b0e55a08bf2f2b98fc6d4fe8cfc3437659a3f42852
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

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