Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Git Bisect: Find the Bad Commit Fast

By Kokil Thapa | Last reviewed: September 2026

A regression lands in production and nobody can name the commit. You need Git Bisect: Find the Bad Commit Fast instead of reading fifty diffs by hand. Git bisect walks your history with a binary search, halving the suspect range on every step until one commit remains. On a Git recovery workflow or a long-lived Laravel branch, that turns days of guesswork into minutes. This guide covers manual bisect, automated git bisect run, and the traps I see on real client projects.

What is git bisect and when should you use it?

Git bisect is a built-in binary search over your commit graph. You give Git two anchors: one commit where the bug does not exist, and one where it does. Git checks out the midpoint, you classify it, and the range shrinks by roughly half each round.

For a history of 1,000 commits, a linear search might need hundreds of checks. Bisect typically needs about ten. That difference matters when a payment callback broke after a Friday deploy, or when a Laravel Livewire booking flow regressed and the team merged daily for two weeks.

Use bisect when you can reproduce the failure reliably and you know a good baseline. Skip it when the bug is flaky, environment-specific, or caused by uncommitted local changes. Fix your working tree first, then bisect.

Git Bisect Binary SearchFull range: 512 commitsgood ←————————————————→ badStep 1: test midpoint256 commits remainStep 2: half again64 commits remainFirst bad commitFound in ~9 steps
Git bisect halves the suspect commit range each step until the first bad commit is isolated.

Bisect works on merge commits too. Git can skip untestable merge commits when you pass --no-merges, which keeps CI runs predictable on busy main branches.

When bisect beats blame and log scanning

  • Cross-cutting regressions: A CSS change broke checkout three modules away.
  • Long gaps: QA reported the bug weeks after the offending merge.
  • Binary outcomes: Tests pass or fail, page loads or 500s, API returns 200 or 422.
  • Release archaeology: Pair bisect with conventional commits to read the final SHA in context.

How do you start a git bisect session to find a bad commit?

Start on a clean working tree. Stash or commit local edits first. A dirty tree can poison every midpoint checkout.

  1. Identify a bad commit — usually HEAD on the broken branch.
  2. Identify a good commit — the last known release tag or CI-green SHA.
  3. Run git bisect start, then mark both ends.
  4. At each checkout, reproduce the bug and run git bisect good or git bisect bad.
  5. When Git stops, read the reported first bad commit and inspect its diff.
# Example: checkout broken branch, ensure clean tree
git switch main
git pull
git status   # must be clean

# Start bisect: bad is HEAD, good is v2.4.0 tag
git bisect start
git bisect bad HEAD
git bisect good v2.4.0

# Git checks out a midpoint — run your repro steps
php artisan test --filter=PaymentCallbackTest

# Classify result
git bisect bad    # if test fails
# OR
git bisect good   # if test passes

# Repeat until Git prints something like:
# abc1234 is the first bad commit

On production Laravel apps I maintain, I often anchor good to the last Deployer release tag. That tag maps cleanly to a known deploy on sister sites sharing the same Linux deployment pipeline.

Pick boundaries that actually test the bug

Your good commit must predate the regression. Your bad commit must show it. If both are too recent, bisect still works but wastes steps inside a tiny range. If good is too old, you walk more history than necessary.

Tags beat vague dates. A tag like release-2026-08-15 beats guessing from a calendar. For hotfix branches, bisect between the last green main SHA and the hotfix tip.

Manual Bisect WorkflowClean treegit statusbisect startmark good/badTest midpointreproduce buggood or badshrink rangeFirst bad commitgit show abc1234Always finish with git bisect resetReturns you to the branch tip you started fromNever leave a detached HEAD on production servers
Manual git bisect workflow: clean tree, mark boundaries, classify each midpoint, then reset.

The official reference is the git-bisect documentation. Keep it open the first time you run a live incident bisect.

Can git bisect run tests automatically instead of manual checks?

Yes. git bisect run executes a shell command at each midpoint. Exit code 0 means good. Exit codes 1 through 125 mean bad. Exit codes 125 through 127 skip the commit when tests cannot run — for example missing migration files on very old SHAs.

# Automated bisect with PHPUnit
git bisect start HEAD v2.4.0
git bisect run php artisan test --filter=PaymentCallbackTest

# Custom script returning proper exit codes
git bisect run ./scripts/check-regression.sh

A minimal check script might curl a local endpoint and grep for an error string:

#!/usr/bin/env bash
set -euo pipefail

php artisan migrate --force --no-interaction > /dev/null
php artisan serve --port=8099 &
PID=$!
sleep 2

if curl -sf http://127.0.0.1:8099/health | grep -q '"ok":true'; then
  kill "$PID"
  exit 0   # good
fi

kill "$PID"
exit 1     # bad

Automated bisect pairs well with fast Composer installs in CI. Run bisect locally with cached vendor trees, or wire the same test command your pipeline already trusts.

Manual vs automated bisect

ApproachBest forExit criteriaRisk
Manual good/badUI bugs, visual regressions, intermittent reproYour judgment after manual testingHuman error mislabels a step
git bisect runUnit tests, API contracts, CLI scriptsShell exit code 0 vs 1–125Old commits lack deps or migrations
CI bisect jobLarge teams, shared repro environmentsSame test command as main pipelineRunner cost on wide ranges
Bisect + skipMerge-heavy historygit bisect skip for untestable SHAsSkips can widen search slightly

For flaky tests, fix or quarantine the flake before bisect. A test that fails 30% of the time will lie to the binary search and send you down the wrong half of history.

git bisect run Automationbisect startgood + bad setCheckout middetached HEADRun scriptexit 0 or 1Git labelsgood or badLoop until first bad commit foundTypically log2(n) iterationsReport: first bad commit SHAgit bisect reset to restore branch
git bisect run loops: checkout midpoint, execute test script, classify by exit code, repeat.

The Pro Git book chapter on debugging with Git walks through run with compile checks — the same pattern applies to PHP test suites.

What are common git bisect mistakes that waste your time?

Most failed bisect sessions come from setup problems, not from Git itself. These show up often when bisecting Laravel apps after a cluster of merges.

Dirty working tree or generated files

Bisect checkouts can overwrite tracked files mid-search. Commit or stash first. Add build artefacts to .gitignore so they do not block checkout. On one production deployment I traced a false bad mark to an uncommitted .env tweak that changed database credentials at each midpoint.

Dependencies that drift across old commits

Checking out a six-month-old SHA with today's vendor/ folder produces nonsense failures. For PHP projects, run composer install after each checkout when lock files changed often. Cache ~/.composer to keep that tolerable. Node frontends need the same discipline if lockfiles moved during the range.

Database and migration mismatch

A commit may expect columns your local DB lacks. Refresh test schema at each step, or point bisect at a disposable Docker database you rebuild per checkout. I prefer a dedicated test database name like bisect_tmp over touching dev data.

Mislabeled good or bad

One wrong label corrupts the search. When unsure, run git bisect log and verify marks. Use git bisect replay to resume a saved session instead of guessing where you stopped.

Git Bisect PitfallsAvoidDirty working treeStale vendor/ node_modulesFlaky or missing testsDo insteadgit stash or commit firstcomposer install per checkoutOne deterministic test commandRescue commandsgit bisect log — review marksgit bisect skip — untestable merge commitsgit bisect reset — abort and return to branch tipgit bisect replay log.txt — resume saved session
Common git bisect mistakes and the rescue commands that fix a derailed binary search.

Pair bisect with Git hooks and secrets scanning in CI so future regressions surface before they reach the good baseline you depend on.

When Git reports the first bad commit, inspect it before you reset:

git show abc1234
git bisect log > /tmp/bisect-session.log
git bisect reset

git bisect reset returns you to the branch you started from. Without it you stay on a detached HEAD — a common post-incident footgun on shared staging servers.

To abort mid-flight:

git bisect reset
# or explicitly
git bisect abort   # alias in newer Git versions

Save logs when the regression spans teams. Paste the log into your ticket so the next developer can git bisect replay without repeating manual steps. That habit mirrors how I document deploy rollbacks on legal-tech portals where audit trails matter.

After you find the bad commit

  • Read the diff — confirm causation, not just correlation.
  • Write a regression test if one did not exist.
  • Cherry-pick a fix or revert the SHA on main.
  • Post-mortem: why did CI miss it? See AI code review in CI for extra signal.
  • Update runbooks on support and maintenance so the next on-call engineer knows the bisect command set.

For merge-heavy workflows, read resolving merge conflicts and branching strategy docs before you widen the bisect range across dozens of parallel feature branches.

When the bug lives in server config rather than app code, bisect the dotfiles repo separately. Application bisect and infrastructure bisect should not share one muddled range.

Developers debugging parser edge cases sometimes validate repro strings in a regex tester before encoding them into a bisect script guard clause. Small tooling habits reduce false bad marks.

If bisect points to a commit that only changed minified assets, rebuild front-end artefacts for that SHA before you label it. Committed Vite 8.x bundles on a Laravel 13.x app must match the PHP code at that point in history.

For deeper post-bisect analysis, AI-assisted debugging can summarize the offending diff once you hold the SHA. Bisect finds the needle; review tools help you explain it to stakeholders.

Supply-chain conscious teams cross-check the bad commit author and signature with signed commits policy before reverting on main.

Key Takeaways

  • Mark a known-good and known-bad commit, then let git bisect binary-search the range — about ten steps for a thousand commits.
  • Keep a clean working tree and reinstall dependencies when lock files changed inside the range.
  • Prefer git bisect run with a deterministic test that exits 0 for good and 1 for bad.
  • Always run git bisect reset when finished so you are not left on a detached HEAD.
  • Save git bisect log output to tickets so teammates can replay the session.
  • Add a regression test after you find the bad commit so bisect stays a one-time rescue tool.

People Also Ask

How many steps does git bisect need?

Git bisect needs at most log₂(n) steps for n commits in a linear range. Roughly ten classifications isolate one commit among a thousand. Merge-heavy histories may need a few extra skip calls when midpoints are untestable merge commits.

Can you bisect across multiple branches?

Bisect walks reachable commits between your good and bad anchors on the current graph. If the fix lives only on a feature branch, fetch and mark SHAs that exist locally. You cannot bisect commits Git never fetched from origin.

Does git bisect work with shallow clones?

Shallow clones may lack midpoints inside the range. Run git fetch --unshallow or deepen with git fetch --depth=N until both anchors and expected midpoints exist. CI shallow clones cause more bisect pain than full local mirrors.

What exit codes does git bisect run expect?

Exit 0 marks good. Exit codes 1 through 125 mark bad. Codes 125 through 127 skip the commit. Anything outside that convention produces unreliable results — wrap your test command in a script that normalizes codes.

Ship faster when regressions hit

Git Bisect: Find the Bad Commit Fast is the difference between guessing and knowing. Binary search turns a vague “something broke last sprint” into one SHA you can revert, test, and explain. On long-running PHP and Laravel systems — the kind I have maintained since 2010 — that speed protects revenue and client trust. If your team wants bisect wired into CI, deploy runbooks, or post-incident hardening, see the testing and optimization service or contact us for a practical review. More Git workflows live on the blog and the home page.

Frequently Asked Questions

Git bisect is Git's built-in binary search over your commit graph. You mark a known-good commit and a known-bad commit, classify each midpoint checkout, and Git halves the suspect range until one commit remains.

Use git bisect when you can reproduce the failure reliably and know a good baseline commit, such as a last green CI SHA or Deployer release tag. It excels on cross-cutting regressions, long gaps between QA reports and merges, and binary outcomes like tests pass or fail. Skip bisect when the bug is flaky, environment-specific, or caused by uncommitted local changes. Fix your working tree first, then start. On production Laravel apps I maintain, bisect beats hand-scanning fifty diffs after a Friday deploy broke a payment callback.

Start on a clean working tree — stash or commit local edits first because a dirty tree can poison midpoint checkouts. Switch to the broken branch, identify bad as HEAD and good as a known release tag like v2.4.0, then run git bisect start followed by git bisect bad and git bisect good. At each midpoint checkout, reproduce the bug and run git bisect good or git bisect bad. When Git stops, inspect the reported first bad commit with git show before resetting. Tags beat vague calendar guesses for boundaries.

At most log₂(n) steps for n commits in a linear range. Roughly ten classifications isolate one commit among a thousand.

Yes. git bisect run executes a shell command at each midpoint and loops until Git finds the first bad commit. Exit code 0 marks good; codes 1 through 125 mark bad. On Laravel projects, a typical command is php artisan test with a filter for the failing test class. You can also wrap a curl health check in a bash script that returns proper exit codes. Automated bisect pairs well with the same test command your CI pipeline already trusts. Fix flaky tests before automating — a 30% failure rate corrupts the binary search.

Exit 0 means good. Exit codes 1 through 125 mean bad. Codes 125 through 127 skip the commit when tests cannot run, such as missing migration files on very old SHAs.

Most failed sessions come from setup problems, not Git itself. A dirty working tree or uncommitted .env tweak can false-mark every midpoint. Checking out a six-month-old SHA with today's vendor folder produces nonsense — run composer install after each checkout when lock files changed. Database schema drift causes failures when old commits expect columns your local DB lacks; use a disposable test database like bisect_tmp. One mislabeled good or bad corrupts the entire search — verify marks with git bisect log and resume saved sessions with git bisect replay instead of guessing where you stopped.

When Git reports the first bad commit, inspect it with git show, save git bisect log to a ticket file, then run git bisect reset to return to your original branch. Without reset you stay on a detached HEAD — a common post-incident footgun on shared staging servers. To abort mid-flight, run git bisect reset or git bisect abort on newer Git versions. Saving logs lets teammates git bisect replay the session without repeating manual steps, which matters on legal-tech portals where audit trails count.

Shallow clones may lack midpoints inside your bisect range. Run git fetch --unshallow or deepen with git fetch --depth=N until both anchors and expected midpoints exist locally.

Bisect walks reachable commits between your good and bad anchors on the current graph. If the fix lives only on a feature branch, fetch and mark SHAs that exist locally. You cannot bisect commits Git never fetched from origin, so ensure both boundary commits are present before starting. For hotfix branches, bisect between the last green main SHA and the hotfix tip rather than guessing from calendar dates.

Manual good and bad classification suits UI bugs, visual regressions, and cases where human judgment after manual testing is the only reliable exit criteria. git bisect run fits unit tests, API contracts, and CLI scripts where shell exit codes are deterministic. The main manual risk is human error mislabeling a step. Automated bisect risks old commits lacking dependencies or migrations. For merge-heavy histories, git bisect skip handles untestable SHAs but can widen the search slightly.

Bisect checkouts overwrite tracked files at each midpoint. Uncommitted edits, generated build artefacts, or a local .env tweak can change behaviour between steps and produce false bad marks. On one production deployment I traced a derailed bisect to uncommitted database credential changes that shifted results at every checkout. Commit or stash first, add build artefacts to .gitignore, and confirm git status is clean before running git bisect start.

Checking out an old SHA with today's vendor folder produces failures unrelated to the bug. When composer.lock changed inside the bisect range, run composer install after each midpoint checkout. Cache your Composer directory to keep reinstalls tolerable. Node frontends need the same discipline if lockfiles moved during the range. If a commit only changed minified assets, rebuild front-end artefacts for that SHA — committed Vite 8.x bundles on a Laravel 13.x app must match the PHP code at that point in history.

Yes, bisect works on merge commits. On busy main branches with heavy merge traffic, pass --no-merges to skip untestable merge commits and keep CI runs predictable. When a midpoint cannot be tested, use git bisect skip — Git widens the search slightly but continues. Merge-heavy histories may need a few extra skip calls compared to a linear range, but binary search still beats reading diffs by hand across dozens of parallel feature branch merges.

Read the diff and confirm causation, not just correlation. Write a regression test if one did not exist, then cherry-pick a fix or revert the SHA on main. Run a post-mortem asking why CI missed it, save the bisect log to your ticket, and update runbooks so the next on-call engineer knows the command set. If bisect points to infrastructure rather than application code, bisect the dotfiles repo separately — mixing application and server config in one range muddles results.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: