TL;DR:

  • git bisect uses binary search to find the exact commit that introduced a bug — typically finding it in 10-12 rounds even across 1,000+ commits
  • git bisect run automates the process with a test script, making it fully hands-off
  • AI assistants are genuinely useful here: generating the bisect test script is often the hardest part, and it’s exactly what they’re good at

You’ve got a bug that definitely didn’t exist three months ago. The feature still kind of works, so it’s not urgent enough to warrant bisecting through your entire commit history by hand — so you file it, label it “regression,” and it sits there for six weeks.

This pattern is extremely common and almost entirely avoidable. git bisect finds the exact commit that introduced a regression in logarithmic time, and with a good test script, it does it automatically while you go make coffee. The part that stopped people from using it — writing that test script — is now something you can reasonably ask an AI to do.

How Git Bisect Works

The idea is binary search over your commit history. You tell git two things: a commit where the bug is present (bad) and a commit where it wasn’t (good). Git checks out the midpoint commit and asks you whether the bug exists there. Your answer cuts the search space in half. Repeat until you’ve narrowed to a single commit.

git bisect start
git bisect bad                    # current HEAD has the bug
git bisect good v2.3.0            # this tag was clean

Git checks out a commit halfway between those two points. You test it, then:

git bisect good   # bug not present here
# or
git bisect bad    # bug is present here

After 10-12 rounds for a 1,000-commit history (log₂(1000) ≈ 10), git identifies the specific commit. That’s usually enough to understand exactly what changed and why.

When you’re done:

git bisect reset   # returns you to the original HEAD

Automating It with git bisect run

Manual bisect is useful but slow. The real power is git bisect run, which takes a shell command or script and uses its exit code to determine good/bad automatically. Exit 0 = good, non-zero = bad.

git bisect run ./test-regression.sh

Git will complete the entire bisect automatically, running your script at each midpoint. This is genuinely hands-off: 10-12 test executions and you’re done.

The catch is writing test-regression.sh. It needs to:

  • Actually test for the specific regression
  • Return the right exit code
  • Handle edge cases (build errors, test timeouts, etc.)
  • Ideally run fast enough that 12 executions take minutes, not hours

This is where AI helps.

Using AI to Write the Test Script

Here’s the workflow. You have a bug: “The CSV export in the reporting module is producing columns in the wrong order since sometime in the last three months.” You want to bisect to find the culprit.

Give an AI assistant the following context:

  • The specific behaviour that’s broken
  • Relevant code paths (file paths, function names if you know them)
  • How the code was previously tested
  • What a passing test should verify

A prompt like this works well:

“I’m trying to write a git bisect test script for a regression. The bug: CSV export from src/reports/exporter.ts produces columns in the wrong order — specifically, the ‘Amount’ column appears before ‘Date’ instead of after. In the good state, columns are Date, Amount, Reference. This was working as of tag v2.3.0. Write a bash script that tests for this specific regression and exits 0 if the export order is correct, exits 1 if it’s wrong, and handles build failures by exiting 125 (which tells git bisect to skip the commit).”

The AI will produce something like:

#!/bin/bash
set -e

# Build — if this fails, skip the commit (exit 125)
npm run build 2>/dev/null || exit 125

# Run the export and capture output
output=$(node -e "
  const { exportCSV } = require('./dist/reports/exporter');
  const csv = exportCSV([{ date: '2026-01-01', amount: 100, ref: 'A1' }]);
  const headers = csv.split('\n')[0];
  console.log(headers);
" 2>/dev/null) || exit 125

# Check column order
if echo "$output" | grep -q "^Date,Amount,Reference"; then
  exit 0   # good — correct order
else
  exit 1   # bad — wrong order
fi

That’s a complete bisect script from one prompt. You might need to adjust it once you understand your specific build process, but the structure is right.

Exit Code 125 is Important

Exit code 125 is the special signal that tells git bisect run to skip the current commit rather than marking it good or bad. Use it when:

  • The build fails (maybe that commit was mid-feature and unbuildable)
  • A prerequisite is missing at that point in history
  • Your test can’t run for an unrelated reason

Without this, a build failure would be misidentified as the bug, and bisect would report the wrong commit.

# Pattern: always wrap build and environment setup
npm run build 2>/dev/null || exit 125

When the History Is Long

If you’re bisecting across a year of commits, each round might take significant time if your test requires a full build. A few optimisations:

Use a fast smoke test. If the full test suite takes five minutes, write a focused test that only checks the specific regression. Even a basic grep on source output is fine if it accurately detects the bug.

Cache the build. Tools like turbo or nx with caching can make repeated builds across similar commits much faster.

Narrow your range first. Do you know roughly when the regression appeared? Check out commits from three months ago, two months, one month to bracket the issue before running bisect. This reduces the range and therefore the number of rounds.

# Narrow the range manually first
git checkout HEAD~90
# test...
git checkout HEAD~45
# test...
git bisect start HEAD HEAD~45   # then run automated bisect in a narrower range

Common Problems and Fixes

“The script works on the current branch but fails on some historical commits.” Likely missing a node_modules refresh or environment dependency that changed. Add npm install --silent 2>/dev/null || exit 125 to your script before the build.

“Bisect says a merge commit introduced the bug.” Likely a rebase that squashed the real change. Look at the commits merged into that point.

“All commits test as bad.” Your good reference is wrong. It never actually had the correct behaviour. Check your assumption about when the regression started.

The Practical Result

A bug that would have taken an afternoon of git log, reading diffs, and manual testing to trace takes 10-15 minutes of setup and a coffee break to fully identify. The commit it fingers gives you the exact lines that changed and the developer who changed them.

From there, understanding the fix is usually fast — the diff is right there. You know what changed, why it was changed (commit message + issue link), and exactly what broke. The archaeology is done.

Bisect has existed since 2005. The reason most developers underuse it isn’t that they don’t know about it — it’s that writing the test script is friction. That friction is now much lower. Next regression you encounter, spend 15 minutes with an AI writing the script and let bisect do the work.