Code churn is how much a file changes over a period of time: lines added, lines deleted, and how many separate commits touched it. High churn concentrated in a small set of files is one of the more reliable signals of where your next production bug will be, and a better one than file size or complexity on their own.

Most teams already sense this. Every codebase has a billing module or an auth middleware that nobody wants to touch, and it's usually the one that gets touched every week. What teams rarely do is measure it. The data is already in git log, and a ranked list of hotspots costs one command.

Short version:

  • Measure lines added plus deleted per file, or commits per file, over 3 to 12 months.
  • Normalise by dividing churned lines by file size. Absolute churn mostly tracks size.
  • Rank them. Files at the top of both the commit list and the line list are the real hotspots.
  • Add ownership. High churn, many authors and no majority author is the worst combination.
  • Act. Owners on the top 20, a higher review bar for PRs touching them, and re-run it quarterly.

Churn, rework and hotspots aren't the same thing

The terms get used interchangeably, and tools define them differently. Here's how they're used in this post.

Term What it measures Window Question it answers
Code churn Lines added and deleted, or commits, per file 3 to 12 months Where does change happen?
Short-term churn (rework) New code reverted or rewritten soon after it was written Days to 2 weeks Was the code right the first time?
Hotspot A file where churn stays high, usually with size, complexity or weak ownership Months Where should review effort go?
Relative churn Churned lines divided by file size Any Is this file changing a lot for its size?

When a tool or report quotes a churn number, check which of these it means before comparing it to anything.

Does code churn predict bugs?

The best-known evidence comes from Microsoft Research. Nachiappan Nagappan and Thomas Ball studied the changes between Windows Server 2003 and its Service Pack 1, roughly 45 million lines of code across 2,465 binaries. Their paper, Use of Relative Code Churn Measures to Predict System Defect Density (ICSE 2005), reports that their churn metrics separated fault-prone from not fault-prone binaries with 89.0 percent accuracy.

The important word is relative. Raw churn mostly tracks size, because big files change more by virtue of being big. The useful measures normalise it: churned lines divided by total lines, and how many weeks a component kept changing. The paper is explicit that absolute churn on its own predicts poorly.

Two things follow for a working team. A file that changes a lot relative to its size is a warning sign: a 200-line module rewritten three times in a quarter deserves more attention than a 5,000-line one that took a handful of edits. And duration matters, since code that keeps changing for weeks is less settled than code that changed once and stopped.

One caveat. The study covers one very large C and C++ codebase, in 2005. Treat 89.0 percent as evidence the signal is real, not as the accuracy you'll get on your repository.

Measuring churn with git

No tool needed for a first pass. The output below is from the Celery repository over the twelve months to November 5, 2026 (commit 6fcdbf9), with tests excluded so the list shows production code.

Commits per file, the simplest measure:

git log --since="12 months ago" --no-merges --name-only --format="" \ -- '*.py' ':!t/' \ | grep -v '^$' | sort | uniq -c | sort -rn | head -8 
 28 celery/canvas.py 25 celery/backends/base.py 22 celery/backends/redis.py 18 celery/app/task.py 17 celery/worker/request.py 16 celery/worker/consumer/consumer.py 16 celery/app/defaults.py 15 celery/app/base.py 

Change '*.py' to your language and ':!t/' to wherever your tests live. The :! prefix is git's pathspec syntax for "exclude".

Lines churned per file. Commit counts treat a typo fix the same as a rewrite, so --numstat counts lines added plus deleted instead:

git log --since="12 months ago" --no-merges --numstat --format="" \ -- '*.py' ':!t/' \ | awk '$1 != "-" { churn[$3] += $1 + $2 } END { for (f in churn) print churn[f], f }' \ | sort -rn | head -8 
852 celery/backends/redis.py 620 celery/canvas.py 611 celery/backends/base.py 470 celery/beat.py 463 celery/app/base.py 389 celery/worker/request.py 288 celery/backends/database/__init__.py 276 celery/app/trace.py 

The $1 != "-" check skips binary files, which --numstat reports as dashes. A file near the top of both lists is a real hotspot. One that shows up in only one list is usually either many tiny edits or one big rewrite.

In Celery's case, 20 of the 161 Python files under celery/ account for 59 percent of all file changes in the year. Most repositories look like this. Change piles up in a small set of files, and those files deserve review time that a flat "every PR gets the same look" process never gives them. To make the numbers relative, divide each file's churned lines by its line count from wc -l.

Which high-churn files are riskiest

Churn tells you where change happens. It doesn't tell you whether anyone understands the file it's happening to. The riskiest files combine high churn, many authors, and no single person who made most of the changes, so every edit gets reviewed by someone holding part of the mental model.

This loop adds author counts and the top author's share:

git log --since="12 months ago" --no-merges --name-only --format="" -- '*.py' ':!t/' \ | grep -v '^$' | sort | uniq -c | sort -rn | head -5 \ | while read -r commits file; do authors=$(git log --since="12 months ago" --no-merges --format='%ae' -- "$file" | sort -u | wc -l) top=$(git log --since="12 months ago" --no-merges --format='%ae' -- "$file" \ | sort | uniq -c | sort -rn | head -1 | awk '{print $1}') printf "%4s commits %3s authors top author %3s%% %s\n" \ "$commits" "$authors" "$((100 * top / commits))" "$file" done 
 28 commits 22 authors top author 14% celery/canvas.py 25 commits 19 authors top author 12% celery/backends/base.py 22 commits 16 authors top author 18% celery/backends/redis.py 18 commits 11 authors top author 38% celery/app/task.py 17 commits 14 authors top author 11% celery/worker/request.py 

For an open-source project like Celery, many authors per file is normal and healthy, and maintainers review every pull request. Inside a company, the same pattern on a billing or auth module means something else. If 19 people changed backends/base.py in a year and nobody made more than 12 percent of the changes, there's probably nobody who can say what that file is supposed to guarantee.

Two caveats on these numbers. Author counts use the commit email, so one person with a work and a personal address counts twice; a .mailmap file fixes that. And a high top-author share isn't automatically good, because it can also mean a bus factor of one.

The cheapest fix is a CODEOWNERS entry for your 10 to 20 hottest files. It puts the person with the most context on every PR that touches them, and in a high-churn file that's most of the PRs.

Does AI-generated code increase churn?

The published data is suggestive rather than conclusive. GitClear analysed 211 million changed lines written between January 2020 and December 2024 and reports increases in short-term churn, alongside two shifts it does quantify: lines associated with refactoring fell from 25 percent of changed lines in 2021 to under 10 percent in 2024, and copy-pasted lines rose from 8.3 percent to 12.3 percent. That's a correlation across many companies over the years AI assistants spread, not a measured cause.

What matters more for review is where the extra change lands. Agents don't change where churn happens, they change how much of it there is. An agent edits wherever the prompt points, and prompts point at the places people already work: the request handler that needs another field, the shared client that needs another option, the config module that needs another flag. Files that were hot get hotter, faster.

The agent also arrives with no history. It doesn't know that request.py has been changed 17 times this year, which of those changes got reverted, or which one started the last incident. A CLAUDE.md or AGENTS.md can describe the architecture, but it rarely says "this file is fragile, be careful here".

A hotspot used to get a handful of changes a month from people who'd been burned by it before. Now it can get several a week, some written by something that has never seen it break.

Reducing risk in high-churn files

  1. Put owners on the top 20. One CODEOWNERS line per file. The cheapest change on the list and usually the most effective.
  2. Raise the review bar for PRs that touch them. A second reviewer, or one from the owning team. Everything else keeps the normal process.
  3. Write the tests the file is missing. High churn plus thin tests is the worst combination. Check coverage on the hotspots, not the repo average.
  4. Split files that change for unrelated reasons. A config module that changes for worker, broker and storage settings is three files sharing one name.
  5. Re-run it every quarter. Hotspots move. A file that cooled off after a refactor shouldn't keep its extra process.

And one thing not to do: don't turn churn into a per-developer metric. High churn against someone's name usually means they were handed the hardest part of the codebase, and measured per person the number gets gamed within a quarter.

Tools

  • Plain git. Free, runs anywhere, and the commands above cover commits, lines and authors per file. You maintain the scripts and do the interpretation.
  • CodeScene. Built around behavioural code analysis, combining change frequency with a code health score. The most direct fit if hotspot analysis is the main thing you want.
  • GitClear. Focuses on how code changes over time, including short-term churn, duplication and moved code. Good for tracking rework trends across a team.
  • Tomosu. Treats churn as one input to a production reliability score rather than a standalone metric.

If you only need a hotspot list once a quarter, git is enough.

On that last one, Tomosu's Production Reliability Index rolls up from eight sub-indexes, and Code Volatility, covering churn and hotspot density, is one of them. It sits next to a Fragility index that looks at timeouts, retries, resource cleanup and error handling in the code, so a hot file with missing timeouts reads differently from a hot file that's mostly config. If you want to see it on your own repo, here's how to run a scan.

Originally published on tomosu.ai.