“It worked last week” is not a bug report. It is a range of commits, and somewhere inside that range is the one that broke it. git bisect turns that range into a single SHA in a handful of steps, and with a test script it does the work while you make tea.
The instinct when a regression appears is to read the diff of everything since the last known-good release and hope the culprit jumps out. On a busy repository that is a hundred commits and several thousand lines, and it usually does not jump out. Binary search is faster and it does not rely on intuition. If good and bad are two hundred commits apart, git bisect finds the offending commit in about eight steps, because each step halves what is left. This walkthrough uses git 2.45; the subcommands have been stable for years, so it applies equally to older installs.
The manual run
Start by telling git where you are and where you were. You need one commit you know is broken (usually HEAD) and one you know worked (a tag, a release SHA, or “last Friday’s build”).
Free · 4 minutes
Is your engineering team shipping safely, or quietly accumulating risk?
Fourteen questions on how work gets from idea to production — cadence, testing, rollback, and the key-person risk in your delivery. Banded finding on screen, full sheet by email.
# begin a bisect session
git bisect start
# the current checkout is broken
git bisect bad
# this release tag was known good
git bisect good v1.8.0
git now checks out a commit roughly halfway between the two and tells you how many revisions and how many steps are left. Build it, test it, and report the verdict:
# reproduce the problem at the checked-out commit, then:
git bisect good # the bug is NOT here
# or
git bisect bad # the bug IS here
Each answer halves the remaining range and git checks out the next midpoint. Keep going until git prints a line ending is the first bad commit, followed by the full commit details. That SHA is your answer: the first commit at which the bug is present. Note the direction convention, because it trips people up. “Good” means old and working; “bad” means new and broken. If your problem is the reverse of a bug — you are hunting the commit that fixed something, or a performance improvement — you can relabel the ends with git bisect start --term-old=fast --term-new=slow and use those terms instead.
Automating it with git bisect run
Doing this by hand is fine for three or four steps. For a wide range, or when the build is slow, hand it a command and walk away. git bisect run executes your command at each midpoint and reads its exit code to decide good or bad automatically.
The contract is precise, and worth getting right: exit 0 means the commit is good; any code from 1 to 127 except 125 means bad; 125 means “cannot test this one, skip it”; and any other code aborts the bisect. That maps neatly onto how test runners already behave — a passing suite exits 0, a failing one exits non-zero — so often you can point run straight at your test command:
# if the whole suite passes on good commits and fails on bad ones
git bisect start HEAD v1.8.0 # bad first, then good
git bisect run pytest tests/test_invoice_totals.py
Passing HEAD and the good ref directly to start saves the two follow-up commands. git churns through the midpoints unattended and stops on the first bad commit. For anything beyond a single test invocation, write a small script and hand git the path to it. This is where you control the exit codes deliberately.
#!/usr/bin/env bash
# bisect-check.sh — return good/bad/skip for the current commit
set -u
# if this commit will not even build, it cannot be judged: skip it
if ! make build >/dev/null 2>&1; then
exit 125
fi
# run the narrowest test that reproduces the regression
if ./run-regression-test.sh >/dev/null 2>&1; then
exit 0 # good: bug absent
else
exit 1 # bad: bug present
fi
chmod +x bisect-check.sh
git bisect start HEAD v1.8.0
git bisect run ./bisect-check.sh
Keep the test narrow. The point of the check is to answer one question — is this specific regression present — as fast as possible, because it runs at every step. A full suite that takes ten minutes turns an eight-step bisect into over an hour; a targeted test that takes ten seconds finishes before you have refilled the kettle.
Commits that will not build
Real history is not uniformly buildable. Some midpoint commit will fail to compile, or sit mid-refactor, or depend on a migration that has not run. Judging it good or bad would be a lie, and a lie here poisons the whole search. That is what exit code 125 is for, and the if ! make build guard above uses it. By hand, the equivalent is git bisect skip. git then picks a nearby commit instead and works around the untestable region. If a cluster of adjacent commits is broken, git will tell you it cannot narrow the answer to a single commit and will name the range instead — which is still a far smaller haystack than you started with.
Finishing cleanly, and keeping the receipt
A bisect leaves your working tree parked on some midpoint commit in a detached HEAD. When you are done — whether git found the culprit or you gave up — put yourself back where you started:
git bisect reset # back to the original branch and HEAD
git bisect reset <commit> # or land on a specific commit
Forgetting this is the single most common way people confuse themselves after a bisect: they keep working on a detached HEAD and later wonder where their commits went. Two more subcommands earn their keep. git bisect log prints the full record of the session — every good, bad and skip — which is worth pasting into the bug ticket as evidence of how the commit was found. And git bisect replay <logfile> re-runs a saved log, so if you fumble a verdict you can correct the log and replay it rather than starting over.
That is the whole tool. It is unglamorous and it is the fastest honest way to answer “which change did this”, which is a question that comes up constantly in incident response. A regression pinned to a specific SHA in minutes is one of the quiet inputs to a low mean time to recovery — you cannot fix fast what you cannot locate fast. The better move still, of course, is to catch the regression before it merges: a targeted test in a pre-commit and CI baseline that actually runs is the same test you would hand to git bisect run, deployed one stage earlier. Write it once; use it in both places.
Build and rescue work
Hands-on delivery of this kind is handled by Sixteen Pillars Studio.
Free interactive tool
Website compliance checklist
What your site has to do, based on what it actually does
Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.
Everything that applies
Ordered by what to do first: legal requirements you can close quickly, then larger pieces of work, then what is expected rather than required. Not exhaustive, and not a legal audit.
Dated PDF, yours to keep or circulate.
Most technology problems are not technology problems. They are control problems.
The systems exist. The investment has been made. The question is whether leadership can understand, direct, evidence, and sustain what those systems produce. Find out where control exists — and where it only appears to.