Git Bisect: The Fastest Way to Find Which Commit Broke Everything

by Arif Ikhsanudin, Backend Developer

The Regression Nobody Can Explain

Your monitoring alerts at 2am. The payment success rate dropped three percentage points sometime in the last 48 hours. Nobody remembers making a change that would cause this. The deployment log shows twenty-seven commits merged in that window — feature work, dependency updates, config changes, a database migration.

The naive approach is to read all twenty-seven commit diffs, form a hypothesis about which one could affect payment success rate, and test it. If you're wrong, you try the next hypothesis. This is slow, error-prone, and gets worse with more commits.

The right approach is git bisect. It performs a binary search through the commit history, identifying the exact commit that introduced the regression in O(log n) steps.

How Binary Search Works Here

If you have 64 commits between a known-good state and the current broken state, bisect will find the bad commit in at most 6 steps (log₂(64) = 6). At each step, bisect checks out the midpoint commit and asks you to tell it whether that commit is good or bad. It then eliminates half the remaining commits from the search space.

64 commits remaining → check midpoint → 32 remaining
32 commits remaining → check midpoint → 16 remaining
16 commits remaining → check midpoint → 8 remaining
8 commits remaining → check midpoint → 4 remaining
4 commits remaining → check midpoint → 2 remaining
2 commits remaining → check midpoint → 1 remaining: the bad commit

Six tests to find one bad commit among sixty-four. Compared to reading all sixty-four diffs, this is the difference between five minutes and two hours.

Running a Manual Bisect

# Start the bisect session
git bisect start

# Tell Git the current state is broken
git bisect bad HEAD

# Tell Git a known-good state (e.g., last week's release tag)
git bisect good v2.3.1

# Git checks out the midpoint commit
# Checking out: a3f9d24... [2 of 7 steps]

Now test whether the bug exists in this checked-out state. For a payment success rate drop, you'd run your integration test suite or manually exercise the payment flow.

# If the bug is present in the checked-out commit:
git bisect bad

# If the bug is NOT present (this commit is fine):
git bisect good

Git checks out the next midpoint. Repeat until Git identifies the commit:

a3f9d24 is the first bad commit
commit a3f9d24
Author: Developer Name <dev@example.com>
Date:   Fri Apr 24 15:32:11 2026 +0700

    chore(deps): upgrade stripe-java from 23.1.0 to 23.5.0

The Stripe SDK version bump caused a behavioral change in payment processing. You would not have found this by reading commit messages alone.

# End the bisect session and return to HEAD
git bisect reset

Automated Bisect

Manual bisect requires you to be present for each step. If you have an automated test that reproduces the bug, bisect can run it automatically:

git bisect start
git bisect bad HEAD
git bisect good v2.3.1

# Provide a script that exits 0 (success/good) or non-zero (failure/bad)
git bisect run ./scripts/test_payment_success.sh

The script runs against each midpoint commit. Git records whether it exited 0 or non-zero, marks the commit accordingly, and proceeds to the next midpoint. The bisect completes entirely automatically and prints the first bad commit.

The script requirements:

  • Exit 0 if the bug is absent (the commit is good)
  • Exit non-zero if the bug is present (the commit is bad)
  • Be deterministic — flaky tests produce incorrect bisect results
  • Handle the case where the test can't run (some intermediate commits may not compile or start)

For the case where an intermediate commit is broken for an unrelated reason:

# Skip this commit — bisect will try an adjacent one
git bisect skip

Or in automated mode, exit with code 125 to signal "skip this commit."

A Practical Test Script

For a service where you're testing whether an API endpoint returns the correct status code:

#!/bin/bash
# scripts/bisect_payment_test.sh

# Build the service (skip if build fails — might be an unrelated commit)
mvn package -q -DskipTests 2>/dev/null
if [ $? -ne 0 ]; then
    exit 125  # skip this commit
fi

# Start the service in background
java -jar target/payment-service.jar &
SERVICE_PID=$!
sleep 3  # wait for startup

# Run the regression test
curl -s -o /dev/null -w "%{http_code}" \
  -X POST http://localhost:8080/api/payments \
  -H "Content-Type: application/json" \
  -d '{"amount": 1000, "currency": "USD"}' | grep -q "200"
RESULT=$?

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

exit $RESULT
git bisect run ./scripts/bisect_payment_test.sh

What Bisect Reveals About Your Commit Quality

Bisect works best when commits are atomic and the test suite is reliable. If your commits bundle multiple unrelated changes, bisect will identify the commit but you'll still have to read a 500-line diff to find the specific change within it. If your test suite is flaky, bisect will mark clean commits as bad and good commits as bad interchangeably.

The usefulness of bisect is partly a function of how well you commit. Small, atomic commits with clear messages make the bisect result immediately actionable: you find the bad commit, read its clear message, look at a thirty-line diff, and understand the problem. Large, bundled commits make bisect find the right haystack while leaving you to find the needle.

Once you've found the bad commit with bisect, the next steps are clear: git show <sha> to read the change, git revert <sha> to undo it while you investigate, and a proper fix once the cause is understood.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Denver's Aerospace Boom Is Great for Engineers — Not So Great for Startups Trying to Hire Them

Colorado's aerospace industry is expanding rapidly and pulling backend engineering talent into long-term careers that startups can't easily interrupt.

Read more

The Contract Clause That Saves You From Scope Creep

Most contractor scope problems are not caused by bad clients — they are caused by contracts that did not anticipate scope changing. One clause handles most of it.

Read more

Employee vs Contractor: The Real Financial Difference

Why that “expensive” contractor rate isn’t as simple as it looks (and why employees aren’t as cheap as they seem)

Read more

Why Cheap Contractors End Up Costing Clients More

The lowest rate is rarely the lowest cost. Clients who have learned this the hard way spend more carefully the next time.

Read more