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.


