Git Bisect
Binary search for bugs — find the exact commit that broke something in O(log n) steps.
Core Bisect Workflow
git bisect start
git bisect bad
git bisect good v0.3.0
You can use a tag, branch name, or commit hash. Git calculates the midpoint and checks it out. You test, mark good or bad, and repeat. For N commits, bisect takes at most log2(N) steps — 1000 commits narrows to 10 steps.
git bisect good
# or
git bisect bad
git bisect reset
Always reset when done. During bisect you’re in detached HEAD state — committing or branching here leads to confusion.
Automated Bisect — The Power Move
git bisect start HEAD v0.3.0
git bisect run pytest tests/test_auth.py -x
Git runs the command at each midpoint. Exit code 0 means "good," non-zero means "bad." The -x flag tells pytest to stop on first failure. Bisect finishes without human interaction and prints the first bad commit.
git bisect start HEAD abc1234
git bisect run sh -c 'make build && ./run-smoke-tests.sh'
The sh -c wrapper lets you chain commands. If the build itself fails on some commits, the non-zero exit marks them bad — which is usually what you want.
git bisect start HEAD v0.2.0
git bisect run sh -c '
make build 2>/dev/null || exit 125
pytest tests/test_graph.py -x
'
Exit code 125 is special: it means "skip this commit — I can’t test it." Git moves to an adjacent commit instead. Use this when some commits don’t compile, have missing dependencies, or are otherwise untestable.
Skip — Handling Untestable Commits
git bisect skip
When a commit is broken for unrelated reasons (won’t compile, missing migration, incomplete merge), skip it. Git picks a nearby commit instead.
git bisect skip abc1234..def5678
If you know an entire range is untestable (e.g., a botched refactor that was fixed later), skip the whole range upfront to avoid wasting steps.
Bisect Log & Replay
git bisect log
git bisect log > /tmp/bisect-session.log
git bisect start
git bisect replay /tmp/bisect-session.log
Replay is useful when you want to demonstrate the debugging process to someone else, or when you need to redo a bisect after realizing your test was wrong. Edit the log file to correct any mis-marked commits, then replay.
Visualization
git bisect visualize
Opens gitk (or git log if gitk isn’t available) showing only the commits still under consideration. Useful for spotting obvious culprits by commit message before continuing the binary search.
git bisect visualize --oneline
When you don’t have a GUI, --oneline gives you a compact list of remaining suspects in the terminal.
Using Old/New Instead of Good/Bad
git bisect start --term-old=fast --term-new=slow
git bisect slow HEAD
git bisect fast v0.2.0
Not every bisect is hunting a bug. If you’re finding which commit introduced a performance regression, "good/bad" is confusing — the commit isn’t broken, it’s just slower. Custom terms make the session self-documenting.
Real-World Example
# 1. Start: HEAD is broken, v0.5.0 was the last known-good release
git bisect start HEAD v0.5.0
# 2. Run automatically against the failing test
git bisect run pytest tests/integration/test_api_endpoints.py::test_create_association -x
# 3. Git outputs something like:
# abc1234 is the first bad commit
# Author: Evan <evan@example.com>
# Date: Mon Apr 7 14:22:00 2026
# refactor: change association dict to use frozenset keys
# 4. Inspect the offending commit
git show abc1234
# 5. Clean up
git bisect reset
The entire workflow — across 200 commits — takes under a minute. Manual debugging would have taken hours. This is the single highest-leverage debugging technique in git.
git bisect start HEAD v0.4.0
git bisect run sh -c '
python -m py_compile src/domus_api/main.py || exit 125
pytest tests/test_graph.py -x
'
The py_compile check catches syntax errors. If the file doesn’t even parse, exit 125 skips that commit. Otherwise, run the real test.