
You pushed a change last Tuesday. By Wednesday, tests were failing. By Thursday, your teammate was asking why the staging server was broken. And now you’re staring at 47 commits wondering which one introduced the bug.
I’ve been there more times than I’d like to admit. Before I learned git bisect, I’d manually check out commits one by one, run the tests, rinse and repeat. It worked, but it was tedious — and when you’re under pressure to fix a production issue, every minute counts.
Git bisect automates that process. It uses a binary search algorithm to walk through your commit history, testing each one until it pinpoints the exact commit where things went wrong. Instead of checking 47 commits one by one, it finds the culprit in about 6 steps. That’s not an exaggeration — it’s math.
What Git Bisect Actually Does
Think of your commit history as a timeline. At some point, everything was working (the “good” state). At another point, it broke (the “bad” state). Git bisect starts in the middle, checks if that commit is good or bad, then halves the remaining range and checks again. It keeps cutting the problem in half until there’s only one commit left — the one that introduced the bug.
If you’ve ever played the number guessing game where someone says “higher” or “lower” after each guess, you already understand binary search. Git bisect does the same thing with your commits.
Prerequisites
You need:
- A Git repository with multiple commits
- A way to test whether the code is “good” or “bad” — this could be a test suite, a manual check, a script, or even opening a browser and clicking around
- Terminal access (works on Linux, macOS, and Windows with Git Bash or WSL)
No special tools or packages required. Git bisect is built into Git itself.
Step 1: Start the Bisect
Open your terminal and navigate to your repository. Start the bisect session by telling Git which commit is bad (usually HEAD, the current state) and which commit was the last known good one:
git bisect start
git bisect bad # Current commit is broken
git bisect good abc1234 # This commit was working fine
You need to know (or find) the hash of a good commit. If you’re not sure, git log --oneline is your friend — scroll back to find a commit you’re confident was working.
Once you run those commands, Git checks out a commit somewhere in the middle of the range and asks you to test it:
Bisecting: 23 revisions left to test after this (roughly 5 steps)
[commit-hash] Commit message here
Step 2: Test and Mark
Now test the code. Run your tests, start the server, reproduce the bug — whatever you need to do to determine if this commit is good or bad.
If the code works fine:
git bisect good
If the bug is present:
git bisect bad
Git immediately jumps to the next commit to test, cutting the remaining range in half. Each step eliminates roughly half the remaining commits. After about log₂(n) steps, you’ll have your answer. For 47 commits, that’s roughly 6 steps. For 100 commits, about 7.
Step 3: Find the Culprit
When bisect finishes, Git tells you exactly which commit introduced the bug:
abc1234 is the first bad commit
commit abc1234
Author: Someone <[email protected]>
Date: Tue Jun 3 14:22:00 2026 +0800
Refactored the authentication middleware
That’s it. You now know exactly when things broke, who made the change, and what the commit message says. From here, you can examine the diff with git show abc1234, revert the change, or fix forward.
When you’re done, clean up:
git bisect reset
This puts you back on the branch you were on before starting.
Automating It With a Test Script
Manually testing each commit works, but you can do better. If you have a script that returns exit code 0 for “good” and non-zero for “bad,” you can let Git run the entire bisect automatically:
git bisect run ./test-script.sh
This is where the real power shows up. Write a simple test script — even a one-liner — and Git will run it at every step without you touching anything. Go grab coffee while it finds the bug for you.
Here’s an example test script that checks if a Python function works correctly:
#!/bin/bash
python3 -c "import app; result = app.calculate(5); exit(0 if result == 10 else 1)"
Make sure the script exits with code 0 when the test passes and non-zero when it fails. That’s the contract Git bisect relies on.
Real-World Example: Finding a Regression in 3 Steps
Let me walk through a concrete scenario. You have a Python app with a calculate() function that should multiply a number by 2. At some point, it started adding 2 instead. You have 10 commits and you know commit 2e78b6e7 was the last good one.
$ git bisect start
$ git bisect bad HEAD
$ git bisect good 2e78b6e7
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[3805dc14] Commit 5
$ # Test it — the function returns 7 instead of 10
$ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[06e0f4c3] Commit 6
$ # Test it — the function returns 10 correctly
$ git bisect good
3805dc14f7ec86cfb1ea6ceca1d3d125f4907f20 is the first bad commit
$ git bisect reset
Three steps. Done. Compare that to manually checking each of the 10 commits one by one.
Troubleshooting Common Issues
“I don’t know which commit was good”
Use git log --oneline --graph to see your commit history. Look for a commit where you remember the feature working. If you really can’t find one, try git bisect start HEAD <oldest-commit-hash> and work backward.
“The build fails on an intermediate commit”
Sometimes a commit in the middle of the range doesn’t even compile — maybe it’s a partial commit or a dependency broke. In that case, skip it:
git bisect skip
Git will try a nearby commit instead.
“Bisect messed up my working directory”
That’s expected — bisect checks out different commits as it searches. When you’re done (or if you want to abort), run:
git bisect reset
This returns you to where you started. If you want to go back to a specific branch:
git bisect reset main
When to Use Git Bisect
Git bisect shines in these scenarios:
- Regression bugs — something worked before and stopped working now
- Flaky tests — a test that used to pass now fails intermittently
- Performance regressions — something got slower and you need to find when
- UI bugs — a visual element broke and you need to find the commit that changed it
It’s less useful when the bug has been present since the beginning of the repo, or when you can’t easily write a pass/fail test for the issue. In those cases, you’re better off with git log and manual inspection.
Pro Tips
- Write your test script before starting. Don’t waste time in the middle of a bisect trying to figure out how to detect the bug.
- Use
git bisect visualizeto see which commits are left to test. It opens a graphical view if you have one configured, or shows a text summary. - Save your bisect state with
git bisect logif you need to pause and resume later. - Combine with
git blame— once bisect finds the commit, usegit blameon the specific file to see exactly which lines changed and who wrote them.
I use this pattern whenever a regression shows up in a project I maintain. It’s saved me hours of manual debugging, and it’s one of those Git features that feels like a superpower once you start using it regularly.
If you’re already comfortable with Git basics like branching and merging, bisect is the next skill that’ll make you dramatically more productive. Pair it with a solid testing setup — something I covered in my pytest testing guide — and you’ll track down bugs faster than ever. If you’re running a local development stack with Docker, my Docker Compose guide covers how to set up reproducible environments that make bisecting even easier.
And if you’re still writing shell scripts to automate your workflow, check out my bash scripting patterns guide for more command-line efficiency tips.