Git bisect: find the commit that introduced a bug in minutes
A bug is in production. It was not there six months ago. There are 400 commits between "working" and "broken." You could manually check commits in the middle, then narrow it down — or you could let git bisect do the binary search for you and find the culprit in 9 steps instead of 400. Here is the complete workflow, including how to automate it so you do not have to do anything except wait.
The manual bisect workflow
Start bisect with a known good commit and a known bad commit:
# Start bisecting
git bisect start
# Mark current state as bad (the bug exists)
git bisect bad
# Mark a known good commit (6 months ago, before the bug)
git bisect good v2.0.0
# or by commit hash
git bisect good abc1234
Git checks out the commit halfway between good and bad. Test if the bug exists:
# Test the current commit...
# Bug exists → mark bad
git bisect bad
# Bug does not exist → mark good
git bisect good
Git checks out the next midpoint. Repeat until git prints: abc1234 is the first bad commit.
# See the full commit details once found
git show HEAD
# End bisect and return to your branch
git bisect reset
With 400 commits, you find the culprit in at most 9 steps (log₂ 400 ≈ 8.6). Without bisect, you might check 200+ commits.
Automated bisect with a test script
If you can write a script that exits 0 for "good" and non-zero for "bad," git bisect can run itself:
#!/bin/bash
# test-bug.sh — exits 0 if bug is absent, 1 if bug is present
# Build (skip if your test does not need it)
npm run build --silent 2>/dev/null
# Run the specific failing test
npm test -- --testPathPattern="checkout" --testNamePattern="total calculation" --silent 2>/dev/null
exit $?
git bisect start
git bisect bad HEAD
git bisect good v2.0.0
git bisect run ./test-bug.sh
Git runs the script on each midpoint commit automatically. When it finds the first bad commit, it prints the commit and exits. You can make coffee.
The skip command for untestable commits
Some commits in the history might not build or have unrelated failures. Tell git to skip them:
# Skip a commit that cannot be tested
git bisect skip
# Skip a range of commits (e.g., a broken CI period)
git bisect skip v2.1.0..v2.1.5
Git will note that the bad commit might be within the skipped range and give you the commits bordering the skip.
Bisecting on a specific file
If you know the bug is in a specific module, limit bisect to commits touching that file:
git bisect start HEAD v2.0.0 -- src/checkout/pricing.ts
Git only steps through commits that modified pricing.ts, which can cut the search space dramatically.
Viewing the bisect log
# See what you have marked so far
git bisect log
# Output:
# git bisect start
# # good: [abc1234] feat: initial v2.0.0 release
# git bisect good abc1234
# # bad: [def5678] fix: update pricing rounding
# git bisect bad def5678
# ...
Real-world example: a failing calculation
A pricing calculation started returning wrong results sometime in the last three months. I do not know when. I write a test script:
#!/bin/bash
# The bug: calculateTotal(items) returns 0 for orders over 10 items
node -e "
const { calculateTotal } = require('./dist/pricing');
const items = Array.from({ length: 11 }, (_, i) => ({ price: 10, qty: 1 }));
const total = calculateTotal(items);
process.exit(total === 0 ? 1 : 0); // exit 1 = bug present
"
git bisect start
git bisect bad HEAD
git bisect good $(git log --oneline --since="3 months ago" | tail -1 | awk '{print $1}')
git bisect run ./test-pricing-bug.sh
# Output after ~8 iterations:
# d7a3f19 is the first bad commit
# Author: Developer Name <dev@company.com>
# Date: Thu Feb 15 14:23:11 2026
#
# refactor: extract pagination helper for item lists
The culprit: a refactor that introduced an off-by-one error in array slicing when length exceeds 10. Found in 8 iterations. Would have taken hours without bisect.
Every engineer should have git bisect as muscle memory. The workflow takes two minutes to set up and saves hours every time a regression sneaks in.