Tags: web-dev concept

Git Bisect

Date: 2026-08-17


Binary search through commit history to find the one that introduced a bug. It turns “somewhere in the last 400 commits” into about nine checks, and it’s the most underused command in git.


git bisect performs a binary search across a range of commits, asking you to test each midpoint until it identifies the first commit where a known-good state became bad.

Why it’s worth knowing

commits to search    checks needed
         10               4
        100               7
      1,000              10
     10,000              14

Doubling the history adds one check. Searching a thousand commits by hand is a day; bisect makes it ten builds — Time and Space Complexity.

The manual loop

git bisect start
git bisect bad                  # current commit is broken
git bisect good v2.4.0          # this tag worked
 
# git checks out a midpoint. Test it.
git bisect good                 # or: git bisect bad
 
# repeat until git reports the culprit
git bisect reset                # back to where you were

git bisect reset is the step people forget, leaving the repository on a detached HEAD in the middle of history — Git Model.

Automating it

The version that makes bisect genuinely cheap:

git bisect start HEAD v2.4.0
git bisect run npm test -- checkout.test.js

Git runs the command at each step and reads the exit code — zero is good, non-zero is bad. Walk away and come back to the answer.

The script doesn’t have to be a test. Anything with an exit code works:

git bisect run ./scripts/check-bundle-size.sh
git bisect run sh -c 'npm run build && node check.js'

Exit code 125 means “cannot test this commit” — use it when a revision doesn’t build for unrelated reasons, and bisect will skip it rather than treating it as bad.

What makes a repository bisectable

Bisect only works if arbitrary commits in history are testable, which is a property you build rather than get:

  • Every commit on main should build and pass. This is the practical argument for CI on every commit rather than only on pull requests — Continuous Integration
  • Atomic commits. A commit doing three things tells you the bug is in one of three places — Commit Hygiene
  • A reliable test. A flaky test makes bisect produce a random commit with full confidence — Flaky Tests
  • Small commits. Bisect narrows to a commit; if that commit changes 40 files you’ve saved less than it appears

Squash-merging every pull request reduces bisect resolution to one commit per feature — usually still a large win, and worth knowing as a cost — Merge vs Rebase.

Beyond bugs

The command searches for any transition, not only breakage:

  • When did the bundle exceed the budget? — Performance Budgets
  • When did this page get slow?
  • When did this test start being flaky? — run it 20 times per step
  • When was this behaviour introduced? — git bisect old/new uses neutral terms for non-bug searches
git bisect start --term-old fast --term-new slow

When it doesn’t work

  • The bug is intermittent. You’ll mark a commit good that isn’t. Run the check several times per step, or accept the result is unreliable
  • The bug depends on external state — a database, an API, a data migration
  • History isn’t buildable — dependency changes that don’t restore cleanly at old commits
  • The change was a configuration or data change, not a commit

The habit worth forming

Reach for bisect before reading code. The instinct is to reason about which change caused a regression; bisect answers it empirically in less time and without a hypothesis.

It’s also the strongest practical argument for the disciplines that make it possible — atomic commits, green main, reliable tests — which pay off here in a way that’s easy to demonstrate.