Git Bisect: Find the Commit That Broke Everything

Suman SharmaSuman Sharma

The login page worked on last month's release. It's broken on main. There are 200 commits in between, and none of them say "break login."

git bisect does a binary search. You tell it one good commit and one bad commit. It checks out the middle one, you say good or bad, and it halves the range again. 200 commits take about 8 tests.

The manual flow

git bisect start git bisect bad # the current commit is broken git bisect good v2.3.0 # this tag or hash was fine

Git checks out a commit halfway between:

Bisecting: 99 revisions left to test after this (roughly 7 steps) [3f9a1c2] Refactor auth middleware

Test it: run the app, run the test, whatever shows the bug. Then tell Git:

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

Repeat until Git prints:

a71e0b4 is the first bad commit commit a71e0b4 Move session cookie to new domain config

Then finish:

git bisect reset # back to where you started

When a commit can't be tested

Some commits won't build, or fail for an unrelated reason. Skip them:

git bisect skip

Git picks a nearby commit instead. If you skip many in a row, it may end with a small range rather than one commit, which is still useful.

Fully automatic: git bisect run

If you can write a command that exits 0 when good and non-zero when bad, Git does the whole search for you:

git bisect start HEAD v2.3.0 git bisect run pnpm vitest run src/auth/login.test.ts

Go and get a coffee. Git checks out each candidate, runs the command, and reports the first bad commit.

Exit codes git bisect run understands:

Exit codeMeaning
0good
1–124, 126, 127bad
125skip this commit (can't test)

So a script can skip commits that don't build:

#!/bin/sh # bisect.sh pnpm install --frozen-lockfile >/dev/null 2>&1 || exit 125 pnpm build >/dev/null 2>&1 || exit 125 node scripts/check-login.mjs
chmod +x bisect.sh git bisect run ./bisect.sh

Tip: if the test that catches the bug is new, it won't exist in old commits. Keep the script outside the repo or untracked, so it stays available as Git checks out older commits.

Hunting for "when did this get faster/slower"

"Good" and "bad" are confusing when you're looking for when something was fixed or changed. Use your own terms:

git bisect start --term-old=slow --term-new=fast git bisect fast HEAD git bisect slow v1.0.0

See and replay what you did

git bisect log > bisect.txt # save your answers git bisect replay bisect.txt # redo them after a mistake

If you marked one commit wrong, save the log, edit out the bad line, reset, and replay.

Make bisect work well for you

  • Small commits. Bisect finds a commit. If that commit changed 3,000 lines, you still have hunting to do.
  • Every commit should build. Squash "WIP" commits before merging, or use --first-parent to test only merge commits on main:
git bisect start --first-parent
  • Tag releases. A tag is the easiest "known good" starting point.

Frequently asked questions

What does git bisect do?

It binary-searches your commit history between a known good commit and a known bad one. Each step halves the range until Git identifies the first bad commit.

How many steps does git bisect take?

About log2 of the number of commits. 100 commits take about 7 steps, 1,000 take about 10.

How do I stop a git bisect?

Run git bisect reset. It ends the session and returns you to the branch you were on before you started.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.