Skip to main content

Git Bisect - Hunt Regressions Fast

The Problem Bisect Solves

Ayesha reports that CSV export is broken. It definitely worked in the v1.4.0 release two weeks ago — and 180 commits have landed since. Reading 180 diffs is a day of work. Checking out commits one by one is worse.

git bisect finds the guilty commit by binary search: it checks out the midpoint of the suspect range, you tell it whether the bug exists there, and it halves the range again. 180 commits take about 8 tests. A thousand commits take 10.

Manual Bisect Walkthrough

Three commands drive the whole session:

Start the hunt
git bisect start
git bisect bad # current HEAD has the bug
git bisect good v1.4.0 # this tag was fine

Git immediately checks out the middle commit and reports its progress:

Bisecting: 90 revisions left to test after this (roughly 7 steps)

Now test — run the app, run one test file, whatever reproduces the bug — and report the verdict:

git bisect bad # bug present at this commit
# or
git bisect good # bug absent at this commit

Repeat. After a handful of rounds Git announces the culprit:

b7d2e85 is the first bad commit
commit b7d2e85
Author: Rizwan Ashiq <mrizwanashiq@outlook.com>
Date: Mon Jun 22 14:03:11 2026

refactor(export): stream rows instead of buffering

Read that one diff, find the bug, and end the session:

Return to where you started
git bisect reset

reset is mandatory — bisect leaves you in a detached HEAD state on some historical commit until you run it.

Can't test a commit?

Sometimes the midpoint commit doesn't even build for unrelated reasons. Skip it and Git picks a neighbor instead:

git bisect skip

Automated Bisect: git bisect run

If the bug is reproducible by a command, you don't need to answer manually at all. Any script that exits 0 for good and non-zero for bad can drive the entire search:

Let a test drive the whole bisect
git bisect start HEAD v1.4.0
git bisect run npm test -- --testPathPattern export
git bisect reset

Git checks out commits, runs the command, interprets the exit codes, and prints the first bad commit — completely unattended. This is my favorite party trick in Git: a regression hunt across two weeks of history reduced to two commands and a coffee break.

Exit code semantics for the script:

Exit codeMeaning
0Good — bug absent
1124, 126127Bad — bug present
125Untestable — same as git bisect skip

A custom script works too:

bisect-check.sh
#!/bin/sh
npm ci --silent || exit 125
node scripts/export-check.js
git bisect run ./bisect-check.sh

The exit 125 line marks commits where dependencies won't even install as "skip" instead of falsely blaming them.

Making Your History Bisect-Friendly

Bisect is only as good as the history it searches. Two habits pay off:

  • Every commit should build and pass tests. A history full of wip commits that don't compile forces constant skipping and can make the result ambiguous. This is a strong argument for squashing messy branches before merge — see Cherry-pick & Interactive Rebase.
  • Small, focused commits. Bisect points at one commit — if that commit changes 40 files across three features, you've only narrowed the search, not finished it.

Quick Reference

git bisect start # begin a session
git bisect bad [commit] # mark a commit as broken (default HEAD)
git bisect good <commit> # mark a known-good commit
git bisect skip # this commit is untestable
git bisect run <cmd> # automate with a command's exit code
git bisect log # show verdicts so far
git bisect reset # end session, return to start