Every repo I’ve worked in has a coverage number, and the number lies. Ours says 84%. It’s said 84% for about a year. It is, in the most literal sense, true — and it tells me almost nothing about the change sitting in front of me.
A project-wide coverage percentage is the HEAD / → 200 of testing. Someone opens a PR, adds a hundred lines with no tests, and the global number drifts from 84.1% to 83.6%. Nobody blocks a PR over half a percent — you’d look insane doing it. So the untested code walks right in, the number stays roughly flat, and six months later “84% covered” is hiding a steadily growing pile of stuff nobody ever wrote a test for. The aggregate is exactly the wrong lens: it’s biggest where it matters least, and it barely flinches at the one thing you’d actually want to catch.
Same instinct as the other gates I keep building — uptime, deploy — pointed at a different lie.
The Idea
Stop grading the whole codebase. Grade the diff.
It’s called delta coverage, or patch coverage, and the move is simple: correlate the git diff with the coverage report and hold only the lines this PR changed to a threshold. The global number can sit at 84% forever — I don’t care. I care that the hundred lines you just wrote are tested. If they’re not, the PR doesn’t merge. Coverage debt can’t slip in behind an aggregate that barely moves, because the aggregate isn’t the gate anymore. Your diff is.
This isn’t a new idea — Codecov and friends have done patch coverage for years. I wanted a version that was dead simple, language-agnostic, and ran entirely inside my own CI.
What I Built
difftron is a small Go CLI that takes a coverage report — LCOV, Cobertura, or Go’s native format — diffs HEAD against the base branch, and reports coverage on the changed lines only. Because it speaks those three formats, it doesn’t care what language you’re in: anything that can emit one of them works. Node, Python, Go, Rust, whatever — if your test run produces an lcov file, difftron can gate it.
Then I wrapped it as a GitHub Action and put it on the Marketplace, so wiring it up is this:
- uses: swantron/difftron@v1
with:
coverage: coverage/lcov.info
threshold: '80'
It posts a sticky comment on the PR with the per-file breakdown and the exact uncovered line numbers, and it fails the check when your changed lines come in under threshold. That’s the whole thing. It’s free, it runs in your CI, and nothing — not your source, not your coverage — leaves the runner.
Dogfooding the fleet
The real test of “language-agnostic” is putting it on things that aren’t the repo it was written in. So I spent an afternoon dropping it onto six of my own: the Go tools (go test -coverprofile and you’re done), a couple of React apps on vitest, and two that needed c8 to wrap a plain node --test into an lcov file. One config shape, six repos, every coverage toolchain I own.
Each one caught the untested code I deliberately threw at it — a tested function and an untested one in the same PR, and difftron credited the first and flagged the second every time, in every ecosystem. Watching a Go repo, a vitest repo, and a node:test monorepo all light up the same sticky comment was the moment it felt real.
The honest bug that fell out of dogfooding: the first version failed a docs-only PR. Someone edits a README, difftron sees a changed file, finds no coverage data for it — because it’s markdown, of course it has none — decides that means 0% covered, and fails the PR. That’s an instant uninstall. The fix is a small bit of philosophy: difftron has no coverage signal for a README or a YAML, so it shouldn’t pretend it does. Files with no coverage data get skipped, not failed, and the comment says exactly which ones it skipped. Touch only docs, and the gate passes clean and tells you why.
The caveats, because there are always caveats
- It’s a gate, not a coverage SLO. It says “the lines in this PR are tested.” It does not say “this codebase is well tested.” Different question, on purpose.
- It only knows what your coverage report tells it. If your test tool doesn’t emit a file for something, difftron can’t grade it — it’ll skip it and say so out loud rather than guess. Garbage in, honestly-labeled garbage out.
- It’s playing in a crowded pool. Codecov, Coveralls, Sonar all do patch coverage, and they have hosted dashboards and org-wide policy that difftron doesn’t. The thing they structurally can’t match: difftron runs entirely inside your CI. No upload, no token, no third party ever seeing your code. For a lot of teams that’s the whole ballgame.
Try It
Marketplace: Difftron Delta Coverage Gate
Source: github.com/swantron/difftron
A few lines of yaml, $0, runs where your tests already run — and your next untested PR doesn’t get to merge quietly. Check it out ^

















