Debugging with Git Bisect
Learn to use git bisect to find the exact commit that introduced a bug. Covers manual bisect, automated bisect with scripts, and practical strategies.
What you'll learn
- ✓How git bisect uses binary search to find bugs
- ✓Manual bisect workflow step by step
- ✓Automated bisect with test scripts
- ✓Strategies for effective bisecting
- ✓Handling tricky situations (merge commits, flaky tests)
Prerequisites
- •Basic Git commands (log, checkout)
- •Ability to verify whether a bug is present
- •Command line familiarity
You know the bug exists now, and you know it did not exist two weeks ago. Somewhere in the 200 commits between then and now, something broke. Checking each commit manually would take forever. git bisect uses binary search to find the exact commit in O(log n) steps — about 8 checks for 200 commits.
How it works
Binary search on commits:
- You mark a “bad” commit (where the bug exists) and a “good” commit (where it does not).
- Git checks out the commit halfway between them.
- You test whether the bug is present and tell Git “good” or “bad.”
- Git narrows the range and checks out the next midpoint.
- Repeat until Git identifies the exact commit that introduced the bug.
For 1000 commits, this takes about 10 steps.
Manual bisect
# Start bisecting
git bisect start
# Mark the current commit as bad (the bug exists here)
git bisect bad
# Mark a known good commit (the bug did not exist here)
git bisect good v2.1.0 # Or use a commit hash: git bisect good a1b2c3d
# Git checks out a commit halfway between good and bad
# Bisecting: 45 revisions left to test after this (roughly 6 steps)
# [abc1234] Add caching layer
# Test the bug. If it's present:
git bisect bad
# If it's not present:
git bisect good
# Repeat until Git identifies the commit:
# abc1234def is the first bad commit
# commit abc1234def
# Author: Alice <alice@example.com>
# Date: Mon Jun 15 14:30:00 2026
#
# Add caching layer
# Done. Clean up:
git bisect reset
Automated bisect
If you have a script or test that can verify the bug, automate the entire process:
# Start
git bisect start
git bisect bad HEAD
git bisect good v2.0.0
# Run automatically with a test script
git bisect run npm test
# Or with a custom script
git bisect run ./test-for-bug.sh
The script must exit with:
- 0 = good (bug not present)
- 1-124, 126-127 = bad (bug present)
- 125 = skip (cannot test this commit, e.g., does not compile)
Example test script
#!/bin/bash
# test-for-bug.sh
# Build the project (skip if it fails to compile)
npm run build 2>/dev/null || exit 125
# Run the specific test that catches the bug
npm test -- --grep "user login should succeed" 2>/dev/null
# The exit code of npm test is passed through:
# 0 = test passes = good commit
# non-zero = test fails = bad commit
chmod +x test-for-bug.sh
git bisect start
git bisect bad HEAD
git bisect good abc1234
git bisect run ./test-for-bug.sh
Git runs the script at each step, automatically marking commits as good or bad. In a few seconds, you have your answer.
Automated bisect with inline commands
For simple checks, use an inline command:
# Find when a file was deleted
git bisect run test -f src/utils/helpers.js
# Find when a string was removed
git bisect run grep -q "function validateEmail" src/validators.js
# Find when a test started failing
git bisect run sh -c "npm install 2>/dev/null && npm test 2>/dev/null"
Practical example: finding a performance regression
You noticed that API response times doubled. You know the last release was fine:
git bisect start
git bisect bad HEAD
git bisect good v3.2.0
# Use a script that checks response time
cat > /tmp/perf-test.sh << 'EOF'
#!/bin/bash
npm install --silent 2>/dev/null || exit 125
npm start &
SERVER_PID=$!
sleep 2
# Make a request and check response time
RESPONSE_TIME=$(curl -o /dev/null -s -w "%{time_total}" http://localhost:3000/api/users)
kill $SERVER_PID 2>/dev/null
wait $SERVER_PID 2>/dev/null
# If response time > 0.5 seconds, it's bad
echo "Response time: ${RESPONSE_TIME}s"
if (( $(echo "$RESPONSE_TIME > 0.5" | bc -l) )); then
exit 1 # bad
else
exit 0 # good
fi
EOF
chmod +x /tmp/perf-test.sh
git bisect run /tmp/perf-test.sh
Handling tricky situations
Commits that do not compile
If a commit does not compile and you cannot test it, skip it:
# Manual
git bisect skip
# Automated (exit code 125 in the script)
npm run build || exit 125
Git works around skipped commits by testing nearby commits instead. If too many consecutive commits must be skipped, Git may not be able to identify the exact bad commit.
Bisecting merge commits
By default, bisect considers all commits including merge commits. To follow only the first-parent chain (the main branch):
git bisect start --first-parent
This is useful when feature branches have many intermediate commits that do not work independently.
Saving and replaying a bisect session
# Save the bisect log
git bisect log > bisect-log.txt
# Replay it later (or on another machine)
git bisect replay bisect-log.txt
Viewing the bisect visualization
git bisect visualize
# Opens gitk showing the remaining commits to test
# Or as text
git bisect visualize --oneline
Using bisect with terms other than good/bad
Sometimes the bug is not about good/bad. Maybe you are looking for when a feature was added, or when behavior changed. Git lets you define custom terms:
# Using old/new instead of good/bad
git bisect start --term-old=slow --term-new=fast
git bisect fast HEAD
git bisect slow v1.0.0
# Then use your custom terms
git bisect slow # This commit is slow
git bisect fast # This commit is fast
Tips for effective bisecting
-
Write a reliable test: The test must consistently pass on good commits and fail on bad ones. Flaky tests will give wrong results.
-
Choose your good commit wisely: The closer the good commit is to the bad commit, the fewer steps needed. Check release tags or deployment markers.
-
Test the endpoints first: Before starting bisect, verify that your “good” commit is actually good and your “bad” commit is actually bad. A wrong starting point wastes time.
-
Isolate the bug: Bisect finds a commit, not the root cause. The identified commit might expose a latent bug introduced earlier. Read the diff carefully.
-
Use
git bisect runwhen possible: Automation is faster, more reliable, and can run unattended.
After finding the commit
Once you know which commit introduced the bug:
# View the commit details
git show <commit-hash>
# See what files changed
git diff <commit-hash>~1 <commit-hash>
# Check the blame for specific lines
git blame <file> | grep <relevant-line>
Then fix the bug, knowing exactly what change caused it.
Summary
git bisect uses binary search to find the commit that introduced a bug in O(log n) steps. Start with git bisect start, mark a known bad commit and a known good commit, and test each midpoint. For full automation, use git bisect run with a script that exits 0 for good, non-zero for bad, and 125 for skip. It is one of the most underused Git commands and one of the most powerful debugging tools available.
Related articles
- Git Git Bisect: Find the Commit That Introduced a Bug
Learn how to use git bisect to perform binary search through your commit history and pinpoint the exact commit that introduced a bug in your codebase.
- Git Advanced Git Stash Techniques
Go beyond basic git stash. Learn partial stashing, stash branches, managing multiple stashes, and recovering dropped stashes.
- Git Git Worktrees: Work on Multiple Branches Simultaneously
Learn to use git worktrees to check out multiple branches at once without stashing or switching. Covers setup, workflows, and practical use cases.
- Git Git Bisect Tutorial: Find the Bad Commit Fast
Use git bisect to binary-search through history and pinpoint the commit that introduced a regression, with manual and automated examples.