top of page

Tutorial 2: Bisect to Find the Cause

  • Contributor
  • 1 day ago
  • 3 min read

"It used to work; now it doesn't" + 500 commits between then and now. Bisect finds the breaking commit in log(N) time.

Step 1: When to Bisect (5 min)

Bisect works when:

  • A specific behavior changed

  • You can clearly tell "broken" vs. "working"

  • You have a known-good and known-bad commit

Not useful for:

  • Intermittent bugs

  • Bugs that have always existed

  • Bugs depending on data state

Step 2: git bisect Basics (10 min)

git bisect start
git bisect bad                    # current commit is broken
git bisect good v1.0.0            # this tag was good

Git checks out the midpoint commit. Test:

# Run your tests / reproduce manually
# If broken: 
git bisect bad
# If working:
git bisect good

Repeats. Each step halves the range.

After log2(N) steps:

abc123def is the first bad commit

git bisect reset to return to where you started.

Step 3: Automate the Test (10 min)

For known reproducer:

git bisect start HEAD v1.0.0
git bisect run pytest tests/test_broken.py

bisect run runs the script at each commit. Exit 0 = good; non-zero = bad. Walks automatically.

For a custom script:

#!/bin/bash
# bisect-test.sh
make build || exit 125    # 125 = skip (can't test)
./run-test

Exit 125 means "can't test this commit; skip."

Step 4: Bisect Beyond Code (10 min)

The same logic works without git:

  • Config: bisect through config changes

  • Data: bisect through versions of input data

  • Environment: bisect through OS / library versions

Halve, test, narrow. Works for any "which change broke it."

Step 5: Be Strict About Good/Bad (5 min)

Vague "kind of works" = wrong bisect result.

For each test:

  • Run the same exact reproduction

  • Decide good/bad on the same criterion

  • If you're unsure, git bisect skip (not good/bad)

Inconsistency = wrong commit identified.

Step 6: Bisect in CI (5 min)

For automated bisects in CI, mark known good with tags:

git tag known-good

When something breaks:

git bisect start HEAD known-good
git bisect run npm test

CI can do this on a schedule for nightly regression tests.

Step 7: Once You Find the Commit (10 min)

git show abc123                  # see the diff
git log -1 --format=%B abc123    # the message
git log abc123 --stat            # files changed

Read the commit. Often the cause is obvious from the diff.

Then:

  • Revert? Patch forward?

  • Test the fix in isolation

  • Add a regression test for next time

Step 8: Bisect with Merges (10 min)

Merges complicate bisect. git bisect walks first-parent history by default.

For complex histories:

git log --oneline --first-parent

Shows merge commits along main. Bisect against these to narrow to a feature branch first; then within the branch.

Step 9: Reverse Bisect (5 min)

Sometimes you want "when was this introduced":

# A bug exists at commit X; check older commits to find when it first appeared
git bisect start X older-commit
# Each step: "is the bug present?"

Same algorithm; semantic is "first occurrence" instead of "last working."

Step 10: Discipline Pays Off (10 min)

Bisect is mechanical. The discipline:

  • Define "broken" before you start (precise reproducer)

  • Don't skip steps because "it can't be that one"

  • Trust the algorithm

A surprised "I would have never guessed this commit" is the most common outcome.

What You Just Did

Bisect: when, how, automated, beyond code, with merges. The fastest way to find regressions.

Common Failure Modes

Vague good/bad. Different criteria each step; wrong commit.

Skipping commits you "know" are fine. Sometimes they're not.

Not testing the reproducer first. Spend an hour bisecting; turns out reproducer was wrong.

Forgetting git bisect reset. Repo stuck in bisect state.

Bisecting a flaky test. Random results; meaningless.

bottom of page