Hunting Down Bugs with git bisect: Binary Search for Your Commits

When a bug slips into production, guessing which commit caused it is a fast way to lose hours. git bisect turns that guesswork into a fast, repeatable binary search through your history, helping you pinpoint the first bad commit using simple “good” and “bad” tests—manually or fully automated.

Table of Contents

You deploy a change.

Something that definitely worked last week is now very, very broken.

You know two things:

  • ✅ It used to work.
  • ❌ It’s broken now.

What you don’t know is which commit introduced the bug.

A lot of teams still handle this by guesswork:

  • “Maybe it was that big refactor?”
  • “Try reverting the logging change?”
  • “What if we go back three days?”

This is slow, stressful, and error-prone. Git already has a tool that’s built for exactly this problem: git bisect.

git bisect does a binary search through your commit history to find the exact commit that introduced a regression. Instead of checking every commit, you mark commits as “good” or “bad” and Git narrows it down in O(log n) steps.

In this article, we’ll walk through:

  • When to use git bisect
  • The basic manual workflow
  • How to automate the search with git bisect run
  • Practical tips, patterns, and pitfalls

When to reach for git bisect

Use git bisect when:

  • You have a regression: something used to work, now it doesn’t.
  • You have a repeatable way to test whether a commit is good or bad (a unit test, integration test, curl command, or manual action).
  • You can identify:
    • A known good commit (the bug wasn’t present)
    • A known bad commit (the bug is present)

Typical scenarios:

  • A deployment last night broke a feature, but your last known-good tag was from last week.
  • A library upgrade sometime in the last 50 commits caused a subtle runtime error.
  • A flaky behavior became consistently broken, and you want to find where it got worse.

If you can say “it worked in commit X, and it’s broken in commit Y,” git bisect can almost always help.


How git bisect works (conceptually)

Think of your commit history as a sorted list:

You tell Git:

  • “This commit here is bad.”
  • “That older commit over there is good.”

Git will:

  1. Check out a commit roughly halfway between them.
  2. You test it and say:
    • good → bug not present
    • bad → bug present
  3. Git chooses the next halfway commit between good/bad.
  4. Repeat until there’s only one possible culprit left.

Instead of eyeballing dozens of commits, you log-walk your way to the bad one in just a handful of steps.


Prerequisites: set yourself up for a good bisect

Before you start:

  1. Make sure your work tree is clean

    If you have local changes, commit or stash them:
  2. Pick a known bad point

    Most often this is HEAD (the current commit):

  3. Pick a known good point

    This can be:

    • The last known-good tag (e.g. v1.2.0)
    • A specific commit hash
    • An older branch point

    Confirm that the bug does not occur in that commit.

The more confident you are about “good” and “bad,” the more reliable and faster the bisect will be.


Manual git bisect workflow step by step

Let’s walk through a simple manual session.

1. Start the bisect session

From your repo:

2. Mark the bad commit

If the current HEAD has the bug:

If not, specify a commit:

3. Mark a known good commit

At this point, Git will check out a commit somewhere between those two.

You’ll see output like:

4. Test and classify each commit

Now, test this commit:

  • Run your app
  • Hit the endpoint
  • Run a test command
  • Whatever reproduces the bug

If the bug is present:

If the bug is not present:

Git checks out the next midpoint commit and tells you how many steps are left. Repeat the test + good/bad marking.

5. When Git finds the culprit

Eventually you’ll get something like:

At this point, you’ve found the first commit where the bug appears.

6. Reset git bisect

When you’re done:

This returns you to the commit where you started (usually your original HEAD).


Skipping “broken” commits

Sometimes a commit can’t be tested:

  • It doesn’t compile.
  • A migration is half-finished.
  • Dependencies are broken for that snapshot.

Instead of forcing “good” or “bad,” you can mark it as skipped:

Git will pick a different commit. If too many commits in a row are skipped, Git might only narrow it down to a range instead of an exact commit, but that’s still much better than guesswork.


Automating the search with git bisect run

Manual bisecting is already powerful, but it gets even better when you automate the “good or bad?” decision.

If you can express your test as a script or command that:

  • exits with 0 → good
  • exits with non-zero → bad

…then git bisect run can do the entire search for you.

1. Write a test script

Example: your API endpoint /health returns HTTP 500 when broken.

Create a script like test-health.sh:

A few notes:

  • Exit 0 if the commit is good (test passes).
  • Exit non-zero (e.g., 1) if the commit is bad (test fails).
  • If you use exit 125, Git treats that like skip for git bisect run.

Make it executable:

2. Start a bisect session as before

3. Let git bisect run do the work

Git will:

  • Check out a midpoint commit
  • Run your script
  • Classify as good/bad/skip based on the exit code
  • Move to the next commit
  • Repeat until it finds the first bad commit

When it finishes, it prints the culprit and leaves you at that commit. Then you can inspect the diff, blame, etc.

Don’t forget to:

after you’re done.


Practical patterns for git bisect run

Here are some common patterns for the command/script you run with git bisect run:

1. Use your test suite

If the regression is covered by tests:

2. Simple shell checks

If the bug is visible via CLI:

3. HTTP checks with curl

If the bug is “this endpoint returns 500 now”:

You might want to wrap that in a script to clean up containers between runs.


Tips, best practices, and gotchas

Keep the test as fast as possible

git bisect run might run your test many times. A test that takes 60 seconds will make the whole bisect painful.

  • Prefer narrow tests over “run the entire suite.”
  • Cache dependencies where possible (e.g., use local node_modules, venvs, or build artifacts).
  • Avoid full environment rebuilds if you can.

Watch out for non-deterministic tests

Flaky tests confuse git bisect. One run says “bad,” the next says “good” on the same commit.

If you suspect flakiness:

  • Add retries or “stabilization” to your test script.
  • Tighten the test to something more deterministic.
  • As a last resort, use manual good/bad decisions instead of run.

Be mindful of environment changes

If the bug is due to:

  • External API changes
  • DB schema changes unrelated to code
  • Environment differences

…then bisecting across that change might be misleading. You might be bisecting environment drift instead of code changes.

Try to reproduce the bug in an environment where only the code is changing.

Use tags and notes once you’ve found the culprit

Once git bisect identifies the first bad commit:

  • Add a note:
  • Reference it in issue trackers.
  • Use it as a teaching moment: commit size, review quality, or test coverage issues often show up in bisect results.

Why teams should use git bisect more often

git bisect is one of those tools that feels “advanced” until you’ve used it once or twice. Then it becomes:

  • A standard part of your incident response playbook
  • A way to reduce blame (“it’s this commit,” not “it’s probably your feature”)
  • A nudge toward better tests, because the more behavior you can test automatically, the more powerful bisect becomes

For teams running frequent releases, git bisect pays for itself very quickly in reduced guesswork and faster mean time to resolution.

Have a project or a problem?

Talk with a senior engineer for practical recommendations—no obligation.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Categories

Get a free consultation from Reliable Penguin

Submit the form—or for immediate service call 866-649-7984.