The Problem
Something is broken. You know it worked three weeks ago. You have 200 commits between "worked" and "broken." Which one introduced the bug?
Manual inspection: hours. Git bisect: minutes.
How Bisect Works
Git bisect performs a binary search through your commit history. You tell it a "good" commit (bug doesn't exist) and a "bad" commit (bug exists). It checks out the midpoint and asks you to test. You say good or bad. It keeps halving the range until it finds the exact culprit.
200 commits = 8 steps to find the bad commit (logโ 200 โ 8).
Basic Usage
# Start bisect
git bisect start
# Mark current commit as bad (bug is present)
git bisect bad
# Mark a known good commit (find the last known working hash or tag)
git bisect good v2.4.0
# or by commit hash:
git bisect good a3f7b2c
# Git checks out the midpoint commit
# Test your application, then:
git bisect good # if the bug is NOT present
git bisect bad # if the bug IS present
# Git keeps narrowing until:
# a8f92b1 is the first bad commit
# commit a8f92b1
# Author: Alice <alice@example.com>
# Date: Mon Jan 15 14:22:07 2026
# ...
# When done:
git bisect reset # returns to original HEAD
Automated Bisect
The real power: run a script that tests for the bug automatically. Git bisect calls the script for each commit โ returns 0 (good) or non-zero (bad).
# Create a test script
cat > /tmp/test-bug.sh << 'EOF'
#!/bin/bash
npm run build 2>/dev/null || exit 125 # skip if build fails
npm test -- --grep "user auth returns 200" 2>/dev/null
exit $?
EOF
chmod +x /tmp/test-bug.sh
# Run automated bisect
git bisect start
git bisect bad HEAD
git bisect good v2.4.0
git bisect run /tmp/test-bug.sh
Git will run your script on each commit automatically and report the exact bad commit โ while you do something else.
Exit Codes
0โ this commit is good1(non-zero) โ this commit is bad125โ skip this commit (build broken, test doesn't apply, etc.)
Skipping Commits
Some commits in the middle won't build cleanly. Skip them:
git bisect skip # skip current commit
git bisect skip v2.6.0..v2.7.0 # skip a range
Git works around skipped commits. It might not find the exact commit, but will narrow it to a small range.
Practical Tips
Use tags as reference points
git bisect good v2.4.0 # much easier than remembering hashes
Tag your releases. You'll thank yourself during bisect.
Write a minimal reproduction first
The better your test script, the better bisect works. A flaky test (passes sometimes, fails sometimes) makes bisect unreliable. Make your reproduction deterministic before running bisect.
Check what bisect found
git bisect log # shows the full bisect session
git show a8f92b1 # see exactly what changed in the culprit commit
Example: Finding a Performance Regression
#!/bin/bash
# test-perf.sh โ exit bad if response time > 200ms
RESPONSE_TIME=$(curl -o /dev/null -s -w "%{time_total}" http://localhost:3000/api/health)
THRESHOLD=0.200
if (( $(echo "$RESPONSE_TIME > $THRESHOLD" | bc -l) )); then
exit 1 # bad โ too slow
else
exit 0 # good โ fast enough
fi
git bisect start
git bisect bad HEAD
git bisect good v3.0.0
git bisect run ./test-perf.sh
Key Takeaways
git bisectuses binary search โ 200 commits takes ~8 steps- Start:
git bisect bad HEAD+git bisect good <last-known-good> - Automate with
git bisect run <script>โ the script exits 0 (good) or 1 (bad) - Use exit code 125 to skip commits where the test can't run
- Always end with
git bisect resetto restore your working state