git bisect is one of the most underused commands in Git — a binary search over your commit history that finds the exact commit that introduced a bug, often in under ten steps even across hundreds of commits.
- A Git repository with a bug that used to work at some known-good point in history.
- Ideally, a way to check "is the bug present" that can either be done by hand at each step, or scripted for full automation.
Step 1: Start a bisect session
git bisect start
git bisect bad # the current commit has the bug
git bisect good v1.4.0 # this earlier tag/commit was known-goodGit checks out a commit roughly halfway between the known-good and known-bad points, and waits for you to test it.
Step 2: Test each checkout and report the result
# Git has checked out a commit halfway through the range
npm test
# If it fails:
git bisect bad
# If it passes:
git bisect goodEach answer halves the remaining range — this is why bisect finds the culprit in roughly log2(n) steps: bisecting 500 commits takes about 9 steps, not 500.
Step 3: Git reports the first bad commit
a1b2c3d is the first bad commit
commit a1b2c3d4e5f6...
Author: ...
Date: ...
Refactor payment validation logic
This is the exact commit that introduced the regression — from here, git show a1b2c3d shows exactly what changed, usually making the actual bug obvious.
Step 4: End the session
git bisect resetThis returns your working directory to the branch/commit you were on before starting — easy to forget, and worth doing immediately after finding the culprit so you're not left in a detached HEAD state.
Automating it entirely with git bisect run
If the check can be expressed as a script that exits 0 for good and non-zero for bad, the entire process runs unattended:
git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run npm testGit checks out each midpoint, runs npm test, reads the exit code, and continues automatically until it reports the exact bad commit — no manual checking required at each step.
Bisecting with a custom script for anything more specific
For a bug that isn't a clean test failure (a specific function returning the wrong value, for instance), write a small script that checks exactly that condition and exits accordingly:
#!/bin/sh
# check-bug.sh
result=$(node -e "console.log(require('./calc').total([1,2,3]))")
if [ "$result" = "6" ]; then
exit 0 # good
else
exit 1 # bad
figit bisect run ./check-bug.shThis generalizes bisect to any bug that can be checked programmatically, not just "does the test suite pass."
Handling commits that can't be tested
Sometimes a commit in the range genuinely can't be built or tested (a broken intermediate state, a WIP commit) — mark it as untestable rather than guessing:
git bisect skipGit picks a different commit to test instead and continues the search, excluding the unskippable one from consideration.
Bisecting a specific file or path only
For a large repository, restricting bisect's consideration to commits touching a specific path can speed up the process and avoid testing irrelevant commits entirely:
git bisect start -- src/payments/This limits bisect's candidate range to commits that actually modified something under src/payments/, which is worth doing whenever you already know roughly where the bug lives — it doesn't change the binary-search logic, just narrows the pool of commits being searched.
Viewing the bisect log and replaying it
Every good/bad decision is recorded, and can be reviewed or replayed — useful for sharing a bisect session with a teammate, or resuming one after being interrupted:
git bisect log > bisect-log.txt
# later, or on another machine:
git bisect replay bisect-log.txtgit bisect replay re-runs the exact sequence of decisions from a saved log, arriving at the same state without needing to manually re-test every commit — genuinely useful for handing off a half-finished bisect session to someone else, or resuming one across a break.
Visualizing where you are mid-bisect
git bisect visualize (or git bisect view) opens a graphical view of the remaining candidate commits — useful for getting a sense of how much range is left, or spotting a commit whose message already hints at the likely cause before finishing the full search:
git bisect visualizeThis doesn't change the search logic at all — it's purely a way to see the current state of the remaining range, which can be reassuring (or occasionally reveal the answer early) partway through a long bisect session.
Common mistakes
- Forgetting
git bisect resetafter finding the culprit, leaving the repository in a detached HEAD state that causes confusion on the next commit or checkout. - Using
git bisect runwith a flaky test script. An intermittently-failing test can make bisect converge on the wrong commit — for anything flaky, either fix the flakiness first or run the check multiple times per commit inside the script. - Testing a commit inconsistently by hand (different browser state, different environment) across manual bisect steps, introducing noise into what should be a deterministic good/bad signal.
- Assuming the "first bad commit" bisect reports is always the actual root cause. It's the first commit where the symptom appears — for a bug surfaced indirectly by an unrelated earlier change, the real fix might belong in a different, earlier commit than the one bisect points to.
Related reading
- Git Rebase vs. Merge: When to Actually Use Each — shares tags: git, devops, programming (same category).
- Git Hooks Explained: Automate Your Workflow — shares tags: git, devops.
- CI/CD Pipelines Explained: From Commit to Production — shares tags: devops.
- Clean Code Principles That Actually Hold Up in Practice — shares tags: programming.
- Big O Notation Without the Math Panic — shares tags: programming.