Back to Blog

The Day I Stopped Guessing and Let Git Bisect Find the Needle in My Codebase

The Day I Stopped Guessing and Let Git Bisect Find the Needle in My Codebase

The clock hit 11 PM, and I was still staring at a broken login form that worked perfectly two weeks ago. I’d manually checked out five different branches and re-ran the build half a dozen times, only to find myself deeper in a maze of 150 commits. I was guessing, and I was losing.

We’ve all been there—the "it worked on my machine" or the "it was fine yesterday" scenario. When the codebase is moving fast, tracking down exactly when a regression crept in feels like finding a contact lens in a shag carpet. You could spend two hours manually checking out random hashes, or you could let git bisect do the math for you.

The Binary Search for Your Sanity

At its core, git bisect is a binary search algorithm. Instead of checking every single commit (linear search), it splits the commit history in half. You tell Git one point where things were definitely broken and one point where things were definitely working.

Git then checks out a commit right in the middle. You test it. If it’s broken, the culprit is in the older half. If it’s fine, the culprit is in the newer half.

If you have 128 commits to search through, you’ll find the exact "breaking" commit in exactly 7 steps. That beats manual guessing every single time.

Putting it into Practice

Let’s say your current main branch is broken. You know for a fact that two weeks ago, at tag v2.0.4, everything was green.

Start the process:

git bisect start

Now, mark the current state as bad:

git bisect bad

Then, tell Git when it was last working:

git bisect good v2.0.4

The moment you hit enter, Git will teleport you to a commit halfway between those two points. Your job is now to run your app, run your tests, or do whatever it takes to see if the bug exists there.

If the bug is still there:

git bisect bad

If it works fine:

git bisect good

Git will keep jumping around until it points its finger at the specific SHA that ruined your night.

The "I Have Better Things To Do" Method (Automation)

Manually checking and typing good or bad is fine for UI bugs that are hard to automate, but if you have a test script that can detect the failure, you can finish the whole process in seconds.

If you have a script (like a Vitest suite or a simple bash script) that exits with 0 for success and non-zero for failure, you can use git bisect run.

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

I watched Git churn through 100 commits in about 30 seconds using this. It felt like magic. It checked out a version, ran the test, recorded the result, and moved on until it found the offender.

The "Wait, This Commit Doesn't Even Compile" Problem

Sometimes, git bisect lands on a commit that is "broken" for reasons unrelated to your bug—maybe someone checked in a syntax error that was fixed two commits later. If you can’t test the current commit because the app won't even start, don't mark it bad. That will throw off the search.

Instead, use:

git bisect skip

Git will pick a nearby commit instead and try to work around the mess.

Getting Back to Reality

Once you’ve found the offending commit, Git will show you the author, the date, and the diff. To get back to your original branch (usually main or develop), you have to end the session:

git bisect reset

Why Bother?

It’s tempting to think, "I can just look at the PRs from the last few days." But humans are bad at seeing side effects. A change in a CSS utility class or a minor update to a dependency can have ripple effects that aren't obvious during a code review.

git bisect removes the ego and the intuition. It treats the history as data. By the time I finally used it that night, I found out the login bug wasn't even in the login logic—it was a global redirect change in a middleware file I hadn't even considered.

Stop scrolling through your Git log. Let the binary search find the needle.