What you'll learn
Quick Answer
Git bisect finds the exact commit that introduced a bug by binary-searching your history between a known-good and a known-bad commit. Paired with git bisect run and an automated test script, it needs zero manual checkouts, it simply runs the script at each step and reads the exit code to decide which half of history to search next.
The problem it solves
Something breaks, a test starts failing, a page errors out, a number comes out wrong, and the change could be any one of the dozens of commits merged since it last worked. Checking out each commit in order and testing it by hand works, but it is linear: fifty commits between known-good and known-bad means up to fifty manual checks. Git bisect replaces that with a binary search, which turns fifty commits into about six checks, log base 2 of 50 rounded up, because it eliminates half the remaining history on every single step instead of one commit at a time.
This is exactly the situation bisect was built for: a regression that nobody noticed right away, so the search space is not the last commit or two, it is a stretch of history that might span days or weeks and several people's work. Doing this manually means checking out a commit, rebuilding, re-running the failing scenario, and repeating, all while trying to keep track of which half of history has already been ruled out. A binary search removes that bookkeeping entirely: at every single step there is exactly one commit to test and one question to answer, good or bad, and git remembers the rest.
How the search actually works
You give bisect two reference points: a commit you know is bad, usually the current HEAD, and a commit you know is good, a release tag or a commit from before things broke. Git checks out the commit exactly halfway between them and hands control back to you. You test that one commit and report the result with git bisect good or git bisect bad, and git checks out the new midpoint of whatever range remains. Repeat until the range narrows to a single commit, which is reported as the first bad commit, with its full message and diff shown automatically.
The one requirement is that the property being bisected changes exactly once across the range, it was true, then became false, and stayed false. If a bug is introduced and then coincidentally masked and re-broken elsewhere in the same range, the binary search assumption breaks down and bisect can point at the wrong commit.
A real run, start to finish
Here is an actual run, not a hypothetical. A tiny repo has six commits; the fourth one silently breaks multiply() by turning it into addition, and two unrelated commits land on top of it afterward:
f5605ca Add square() (HEAD, bad)
893136a Add README (bad)
cc77276 Refactor multiply()... (bad - the actual bug)
15f9c09 Add divide() (good)
dd23e71 Add subtract() (good)
7875c1e Add calc.js with add()... (good - start)A one-line script checks that multiply(3, 4) equals 12 and exits 0 or 1 accordingly. Starting bisect and handing it that script:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good 7875c1e
Bisecting: 2 revisions left to test after this
$ git bisect run node check.js
running 'node' 'check.js'
PASS: calc.js behaves correctly
Bisecting: 0 revisions left to test after this
running 'node' 'check.js'
FAIL: multiply(3, 4) should be 12, got 7
Bisecting: 0 revisions left to test after this
running 'node' 'check.js'
FAIL: multiply(3, 4) should be 12, got 7
cc77276 is the first bad commit
Refactor multiply() to use repeated addition
calc.js | 3 ++-Three automated checks, out of six commits, and git named the exact commit and showed the exact diff that introduced the bug, no manual checkout, no guessing which of six commits to inspect first.
Automating it with git bisect run
git bisect run is what turns bisect from a manual back-and-forth into a fully automated search: give it a command, and it checks out each midpoint, runs the command, and reads the exit code to decide good or bad by itself, with no further input needed. The contract is specific: exit code 0 means good, any code from 1 to 127 except 125 means bad, and 125 means skip this commit, used when a commit cannot be tested at all for reasons unrelated to the bug, such as it does not build at that point in history.
The script can be anything runnable from the command line, a test file, a shell script, a one-off Node or Python file like the one above. The only real requirement is that it reflects the exact behaviour being chased and nothing else; a script that also fails for unrelated reasons, a flaky test or a network call, will make bisect report the wrong commit with complete confidence.
A few things that make it more reliable
Keep the test script as narrow as the bug itself, testing only the one behaviour that broke rather than the whole suite, both because it runs faster per step and because an unrelated failure elsewhere would derail the search. Use git bisect skip, or exit code 125 in a script, for any commit that genuinely cannot be tested, rather than guessing good or bad, which would corrupt the search. Always run git bisect reset once the answer is found, since it returns you to the branch you started on; bisect leaves you in a detached-HEAD state on whatever commit it last checked out.
Bisect is not limited to pass or fail bugs either. Point the script at a performance check that exits 1 when a benchmark exceeds a time threshold, and the exact same binary search finds the commit that introduced a slowdown, not just a commit that introduced a wrong answer.
