← Back to Blog

Git Bisect: Find the Exact Commit That Broke Your Code

Something is broken in production. The bug was not there three weeks ago. You have 400 commits in between. Checking each one manually would take days. Git bisect uses binary search to find the culprit in logarithmic time - typically under 10 steps regardless of history size.

The Problem: Regression in a Sea of Commits

A regression is a bug that was introduced at some specific point in time - before that point the software worked, after it did not. The challenge is that a typical production codebase accumulates dozens of commits per week, and a regression might not be noticed for days or weeks after it was introduced.

Given 400 commits between a known-good version and a known-bad version, a naive linear search requires checking up to 400 states. Binary search reduces this to log2(400) ≈ 9 steps. That is the mathematical guarantee git bisect exploits.

At each step, Git checks out the commit at the midpoint of the remaining suspect range. You test it and tell Git whether it is good or bad. Git halves the search space and repeats. After about 9 rounds, it identifies the exact first bad commit.

Prerequisites: What You Need

Before starting a bisect session, you need two things:

  • A known-bad commit: Usually the current HEAD, or a specific commit hash where the bug is confirmed present.
  • A known-good commit: A tag, branch, or SHA where you are confident the bug did not exist. This can be a release tag (v1.5.0), a date-based ref, or simply a commit hash from two weeks ago.

You also need a reliable way to test whether any given commit has the bug - a test case, a script, or a manual reproduction step. The more automated the test, the more you can use git bisect run to automate the entire process.

Manual Bisect: Step-by-Step

Here is a complete walkthrough of a manual bisect session. The bug: the user's cart total is calculated incorrectly. It was fine at the v2.3.0 tag but is broken on the current HEAD.

  1. Start the bisect session.
git bisect start
  1. Mark the current state as bad.
git bisect bad           # current HEAD is broken
  1. Mark the last known good state.
git bisect good v2.3.0   # this tag was confirmed working

Git immediately responds with something like:

Bisecting: 199 revisions left to test after this (roughly 8 steps)
[a4b2c1d] Refactor: extract discount calculation service

Git has checked out commit a4b2c1d, the midpoint. Now you test it.

  1. Test the current checkout. Build the app, run your test, or manually reproduce the bug. Then report the result:
git bisect good   # this commit does NOT have the bug
# or
git bisect bad    # this commit HAS the bug
  1. Repeat. Git checks out the next midpoint and shows how many steps remain. Continue until Git announces the culprit:
e7f3a9b2c1d is the first bad commit
commit e7f3a9b2c1d
Author: Jane Smith <jane@example.com>
Date:   Thu Mar 12 14:22:01 2026 +0000

    Apply 10% discount before tax instead of after
  1. End the session.
git bisect reset   # returns you to the original branch HEAD

Always run git bisect reset when finished. Forgetting to reset leaves your repository in a detached HEAD state, which will confuse you the next time you try to commit.

Automated Bisect with a Test Script

The real power of git bisect comes from automating the test step. Instead of manually checking each midpoint, you provide a script that exits with code 0 for good and non-zero for bad. Git runs it automatically at each step.

# One-liner: start, mark bad (HEAD) and good (v2.3.0), run tests automatically
git bisect start HEAD v2.3.0
git bisect run npm test -- --testPathPattern="cart.test.js"

Git runs npm test -- --testPathPattern="cart.test.js" at each midpoint commit. If the test passes (exit 0), the commit is good. If it fails (non-zero exit), the commit is bad. The bisect terminates automatically and prints the first bad commit.

For more complex scenarios, write a dedicated test script:

#!/bin/bash
# bisect-test.sh - exit 0 if good, exit 1 if bad

# Build the project (skip if not needed)
npm run build --silent 2>/dev/null

# Run the specific test
if npm test -- --testNamePattern="cart total calculation" --silent 2>/dev/null; then
  exit 0   # good commit
else
  exit 1   # bad commit
fi
chmod +x bisect-test.sh
git bisect start HEAD v2.3.0
git bisect run ./bisect-test.sh

Skipping Commits

Sometimes a commit in the search range cannot be tested - maybe it does not compile, or it has a different unrelated bug that makes the test unreliable. Use git bisect skip to tell Git to pick a nearby commit instead:

git bisect skip          # skip the current checkout
git bisect skip abc1234  # skip a specific SHA in the range

Skipping a commit causes Git to check out a nearby alternative. If too many commits are skipped, bisect may not be able to identify the exact bad commit - it will narrow it down to a small range and tell you it cannot determine the exact one.

Review the Culprit Commit's Diff

Once bisect identifies the bad commit, paste its diff into our Diff Checker to review the changes side by side before writing your fix.

Open Diff Checker

Reading the Bisect Log

During a session, Git maintains a log of every step. View it at any time:

git bisect log

The log shows each tested commit and whether you marked it good or bad. This is useful for reviewing your session or sharing the investigation with a colleague. You can also save and replay a session:

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

# Replay it later (useful for reproducing the investigation)
git bisect replay bisect.log

Bisect with Specific File Paths

If you know the bug is in a specific subsystem, you can limit the bisect range to commits that touched certain files. This reduces the number of steps significantly in large codebases:

git bisect start HEAD v2.3.0 -- src/cart/ src/pricing/

Git will only consider commits that modified files under src/cart/ or src/pricing/. Commits outside those paths are automatically skipped.

After Finding the Bad Commit

Once bisect identifies the culprit commit, your workflow is:

  1. Run git bisect reset to return to your working branch.
  2. Inspect the commit: git show <bad-sha> - read the diff and understand what changed.
  3. Check who wrote it: git log --format="%an %ae" -1 <bad-sha> - the author may have context on why the change was made.
  4. Write a regression test that specifically covers the bug before fixing it.
  5. Fix the bug, referencing the culprit SHA in the fix commit message for traceability.
git show e7f3a9b   # inspect the bad commit
git log -1 --format="%an (%ae) on %ad" e7f3a9b   # who and when

FAQ

What exit codes does git bisect run expect from my test script?

Exit code 0 means the commit is good (bug is absent). Any non-zero code (1–127) means the commit is bad (bug is present). Exit code 125 is special: it tells git bisect to skip the current commit rather than mark it bad. Use exit 125 in your script when the commit cannot be tested - for example, when the build fails for an unrelated reason.

Can git bisect find performance regressions, not just functional bugs?

Yes. Your test script just needs to return exit 0 for acceptable performance and non-zero for degraded performance. For example, time a benchmark and compare it to a threshold:

#!/bin/bash
TIME=$(node benchmark.js 2>/dev/null | grep "ms" | awk '{print $1}')
[ "$TIME" -lt 500 ] && exit 0 || exit 1

What happens if git bisect cannot find a single bad commit?

If you skip too many commits, bisect may report a small range of commits where the bad one likely lives rather than a single SHA. You will then need to inspect those commits manually. This is still far better than searching manually through hundreds of commits.

Does git bisect work on merge commits?

Yes, but the results may point to a merge commit itself if the regression was introduced by the merge (e.g., a conflict resolution went wrong). Inspect the merge commit's diff with git show --diff-merges=first-parent <sha> to see what changed specifically due to the merge versus the merged branch.

Can I pause and resume a bisect session?

Yes. Bisect session state is stored in .git/BISECT_* files. You can safely close your terminal, switch tasks, and return later. Just make sure you are in the same repository directory and continue with git bisect good or git bisect bad. The session persists until you run git bisect reset.

Is there a GUI for git bisect?

GitKraken, SourceTree, and VS Code's GitLens extension offer graphical interfaces for bisect. That said, the command-line workflow is faster once you are comfortable with it, and the git bisect run automation is only available from the command line.

Use our free tool here → Diff Checker to compare the bad commit's diff against the working version once bisect identifies the culprit.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.