When production breaks but you have no idea which commit did it
Sprint’s done, deployment goes out, and 30 minutes later Slack explodes: “There’s a bug — Feature A is broken!” You run git log… 47 commits in the sprint. Start reviewing them one by one? There goes your entire afternoon.
This happens more often than it should — on a 4-person microservices project, every sprint piles up dozens of new commits. The instinct is to git checkout each commit and test manually. Then I remembered git bisect — something I’d read about but never actually touched.
After that first time using bisect to track down a bug in 5 minutes instead of 3 hours, I never went back to the old way.
How git bisect works
The idea is simple: instead of checking commits one by one in order, git bisect splits the commit history in half and asks: “Does this commit have the bug?” You answer “yes” or “no”, and it keeps halving the remaining range. This is exactly binary search.
With 1000 commits, the worst case manually is 1000 checks. With bisect, the worst case is log₂(1000) ≈ 10 checks. With 47 commits? About 6 checks and you’re done.
The whole thing revolves around two simple labels:
- bad: a commit that has the bug (usually the current HEAD)
- good: a commit without the bug (an older commit you know was working fine)
Git bisect automatically checks out the middle commit, you test it and mark it bad or good — repeat until the culprit is isolated.
Step-by-step walkthrough with a real example
Starting a bisect session
Say you’re on the main branch, HEAD is the latest commit, and it has a bug:
# Start bisect
git bisect start
# Mark the current commit as bad (has the bug)
git bisect bad
# Mark an older commit as good — use a release tag or a specific hash
git bisect good v1.2.0
# or use a hash
git bisect good abc1234
Git immediately checks out the middle commit and tells you how many steps remain:
Bisecting: 23 revisions left to test after this (roughly 5 steps)
[f3d9a2b] Fix login validation
The test → mark loop
At each commit git brings you to, run your tests or manually reproduce the bug, then mark it:
# If this commit HAS the bug
git bisect bad
# If this commit does NOT have the bug
git bisect good
Keep going until git reports it found the culprit:
e4f5c6d is the first bad commit
commit e4f5c6d
Author: Nguyen Van A <[email protected]>
Date: Mon Feb 10 14:32:11 2026 +0900
Refactor user authentication module
:040000 040000 abc... def... M src/auth
There it is. Commit e4f5c6d is the culprit.
Finishing and returning to HEAD
git bisect reset
This brings you back to your original HEAD. Always run this when you’re done — otherwise you’re sitting somewhere in the middle of history, which is easy to lose track of.
Automating with a script
This is my favorite part. Instead of manually testing each commit, you write a script and let git bisect run it automatically:
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
# Run the script automatically — exit code 0 = good, non-zero = bad
git bisect run ./test_bug.sh
The test_bug.sh file is as simple as:
#!/bin/bash
# test_bug.sh — check whether the login feature works correctly
cd /path/to/project
python -m pytest tests/test_auth.py::test_login -x -q
# exit code comes automatically from pytest: 0 = pass, 1 = fail
Or with Node.js:
#!/bin/bash
npm run build 2>/dev/null && node -e "
const auth = require('./dist/auth');
const result = auth.validateUser('[email protected]', 'password');
process.exit(result ? 0 : 1);
"
Git bisect run will automatically execute this script on each commit and mark it bad or good based on the exit code. You just sit back and watch.
Viewing the bisect log
To review which commits were visited during the session:
git bisect log
If you need to include the investigation trail in a bug report, this is what to attach — your team lead won’t need to ask any follow-up questions.
Skipping commits you can’t test
Sometimes you hit a commit that won’t build — outdated dependencies, missing config, or unresolved conflicts. In that case:
git bisect skip
Git skips over it and tries a neighboring commit.
Practical notes from real experience
Pin down your “good commit” accurately: I use git tag to mark releases. No tags? Estimate: “This feature was working fine two weeks ago.” Then run git log --oneline --since="2 weeks ago" to find a suitable hash.
Unit tests are the force multiplier for bisect: On projects with solid test coverage, writing a bisect run script that targets the relevant test case is the fastest approach by far. I once tracked down a bug in just 8 minutes this way — fully automated, no babysitting required.
Watch out for merge commits: Bisect sometimes lands on a merge commit. You’ll need to look at both parent branches to understand the proper context instead of jumping to conclusions.
On the topic of caution — I once lost important code due to a force push on the wrong branch, and since then I’ve been much more deliberate with every git operation. Same goes for bisect: once you’ve identified the bad commit, don’t rush to git revert it. Read the diff carefully first. A lot of the time that commit touched multiple things at once, and reverting all of it introduces a completely different set of problems.
A practical workflow for handling bug reports
After debugging with bisect many times, I’ve settled on a consistent flow:
- Confirm the bug still exists at HEAD — run
git statusto make sure you’re on the right branch - Find the “last known good” — usually the most recent release tag or the commit just before the current sprint
- Write a minimal test case that reproduces the bug if possible (to use with
bisect run) - Run
git bisect start+git bisect runif you have a script, or mark manually if not - Read the bad commit’s diff carefully with
git show <hash>before writing a fix git bisect resetback to HEAD before you start writing the fix
Conclusion
Most developers know git bisect exists — but ask “when did you last use it?” and you get silence. That’s a waste.
After six months of regular use, bisect shines most in two situations. First, regression bugs after a long sprint with many commits. Second, large codebases where you don’t have enough context to know where to even start reading the code.
You don’t need to memorize complex syntax — just remember: bisect start → mark bad/good → test each commit → bisect reset. Next time you hit a bug and have no idea which commit caused it, try bisect instead of manually sifting through git log and see what happens.

