Skip to content
Codeloom
Git

Finding Bugs with Git Bisect: A Practical Guide

Use git bisect to binary search through your commit history and pinpoint the exact commit that broke your code. Covers manual, automated, and advanced bisect workflows.

·8 min read · By Codeloom
Intermediate 11 min read

What you'll learn

  • How git bisect performs binary search on commits
  • Running manual bisect sessions step by step
  • Automating bisect with test scripts
  • Handling merge commits and skipping untestable commits
  • Real-world debugging strategies with bisect

Prerequisites

  • Basic Git commands (log, checkout, commit)
  • Ability to identify and reproduce a bug

Why Binary Search Your Commits

You shipped a release last Friday and everything worked. On Monday you pull the latest changes — 150 new commits — and the search feature is broken. Nobody knows which commit caused it. Testing each one manually would take hours.

git bisect applies binary search to your commit history. Instead of checking 150 commits, you check about 7 (log2 of 150). Each step cuts the remaining range in half until Git identifies the single commit that introduced the bug.

150 commits between good and bad
Step 1: check commit 75  -> bad  (range: 1-75)
Step 2: check commit 37  -> good (range: 37-75)
Step 3: check commit 56  -> bad  (range: 37-56)
Step 4: check commit 46  -> good (range: 46-56)
Step 5: check commit 51  -> bad  (range: 46-51)
Step 6: check commit 48  -> good (range: 48-51)
Step 7: check commit 49  -> BAD  <- first bad commit found!
Binary search cuts the problem in half each step

Manual Bisect: The Basic Workflow

Step 1: Start the session

git bisect start

Step 2: Mark the bad commit

Tell Git where the bug exists. Usually this is HEAD:

git bisect bad

Or specify a commit explicitly:

git bisect bad abc1234

Step 3: Mark a known good commit

Identify a commit where the bug did not exist. Use a tag, a date, or a commit hash:

# Using a tag
git bisect good v2.3.0

# Using a commit hash
git bisect good f7e8d9c

# Using a date-based reference
git bisect good main@{2026-06-30}

Git immediately checks out the midpoint commit:

Bisecting: 74 revisions left to test after this (roughly 7 steps)
[d4a5b6c] Refactor search indexer

Step 4: Test and mark

Test whether the bug exists at this commit. Then tell Git:

# If the bug is present at this commit
git bisect bad

# If the bug is NOT present at this commit
git bisect good

Git checks out the next midpoint and prints how many steps remain. Repeat until Git finds the first bad commit:

d4a5b6c7890 is the first bad commit
commit d4a5b6c7890
Author: Alice <alice@example.com>
Date:   Sat Jul 5 14:30:00 2026 -0500

    Add fuzzy matching to search results

 src/search/indexer.js | 42 ++++++++++++++++++++----
 1 file changed, 36 insertions(+), 6 deletions(-)

Step 5: Reset

Always reset when done to return to your original branch:

git bisect reset

Automated Bisect with Scripts

Manual bisect works, but automated bisect is far more powerful. Write a script that tests for the bug and let Git run the entire session unattended.

Exit code convention

Your test script must use these exit codes:

  • 0 — the commit is good (bug not present)
  • 1-124, 126, 127 — the commit is bad (bug present)
  • 125 — the commit is untestable (skip it)

Example: Testing with your test suite

git bisect start HEAD v2.3.0
git bisect run npm test

This runs npm test at each midpoint. If the tests pass (exit 0), the commit is marked good. If they fail (exit non-zero), it is marked bad.

Example: Custom test script

Create a script that checks for the specific bug:

#!/bin/bash
# test-search-bug.sh

# Build the project (skip if build fails)
npm run build 2>/dev/null || exit 125

# Start the server in background
npm start &
SERVER_PID=$!
sleep 3

# Test the search endpoint
RESULT=$(curl -s http://localhost:3000/api/search?q=test | jq '.results | length')

# Clean up
kill $SERVER_PID 2>/dev/null

# Check the result
if [ "$RESULT" -gt 0 ]; then
    exit 0   # Good: search returns results
else
    exit 1   # Bad: search is broken
fi

Run it:

chmod +x test-search-bug.sh
git bisect start HEAD v2.3.0
git bisect run ./test-search-bug.sh

Git runs the script at each step, marks commits automatically, and reports the first bad commit.

Example: Using a single test file

If you know which test covers the bug:

git bisect start HEAD v2.3.0
git bisect run npx jest --testPathPattern="search.test.js"

Handling Tricky Situations

Untestable commits

Some commits might not compile or have broken dependencies. Use git bisect skip:

# During manual bisect
git bisect skip

# In automated scripts, exit with code 125
exit 125

Git will try adjacent commits instead. If too many consecutive commits are untestable, Git may not find the exact culprit but will narrow the range.

Merge commits

Bisect works on the linear commit history, so merge commits are included. If a merge commit itself is the culprit, bisect will find it. To investigate which side of the merge introduced the problem, run bisect again within that merge’s range:

# After bisect identifies merge commit abc1234
git log --oneline abc1234^1..abc1234^2   # Commits from the merged branch
git bisect start abc1234 abc1234^2       # Bisect within the merge

Bisect with path limiting

If you know the bug is in a specific directory, you can speed things up by testing only relevant changes:

git bisect start HEAD v2.3.0 -- src/search/

This limits bisect to commits that touched files in src/search/.

Viewing Bisect Progress

During a session, inspect the current state:

# See the bisect log
git bisect log

# Visualize the remaining range
git bisect visualize --oneline

The log is useful for reproducing a session or sharing it with your team:

# Save the log
git bisect log > bisect-session.log

# Replay a saved session
git bisect replay bisect-session.log

Real-World Debugging Strategy

Here is a practical workflow combining bisect with other debugging techniques:

1. Reproduce the bug and define a test

Before bisecting, make sure you can reliably reproduce the bug and determine pass/fail:

# Can you reproduce it?
npm start
curl http://localhost:3000/api/search?q=test
# Empty response -- bug confirmed

2. Find the known good point

# Check when the feature last worked
git log --oneline --since="2026-06-28" -- src/search/
# f7e8d9c 2026-06-29 Update search config
# a1b2c3d 2026-06-28 Release v2.3.0

# Verify v2.3.0 is actually good
git stash  # if needed
git checkout v2.3.0
npm install && npm start
curl http://localhost:3000/api/search?q=test
# Results returned -- confirmed good

3. Run automated bisect

git bisect start HEAD v2.3.0
git bisect run ./test-search-bug.sh

4. Analyze the bad commit

# Bisect found commit d4a5b6c
git show d4a5b6c
git diff d4a5b6c^ d4a5b6c

5. Fix and verify

git bisect reset
git checkout -b fix/search-results
# Apply the fix
git commit -m "fix: restore search results after fuzzy matching change"

Tips for Effective Bisecting

  1. Write deterministic tests. Flaky tests make bisect unreliable. If a test sometimes passes and sometimes fails, bisect will give wrong results.

  2. Use exit code 125 generously. If a commit does not compile, does not have the feature you are testing, or has unrelated failures, skip it rather than marking it good or bad.

  3. Start with a wide range. It is better to include too many commits than to accidentally exclude the bad one. Binary search handles large ranges efficiently.

  4. Automate the build step. Your test script should handle building the project, not just running tests. Older commits may need different build steps.

  5. Combine with git blame. After bisect finds the commit, use git blame on the affected file to understand the change in full context:

git blame -L 45,65 src/search/indexer.js
  1. Log your sessions. Use git bisect log to save and share your debugging process. It helps teammates understand how a bug was tracked down.

Wrap-Up

git bisect turns hours of debugging into minutes. The binary search algorithm guarantees you will find the offending commit in logarithmic time, and automated bisect with git bisect run makes the process entirely hands-off. The hardest part is writing a reliable test script — once you have that, bisect does the rest. Start using it the next time someone says “it worked last week” and nobody knows what changed.