DevOps & Workflow5 min read

    How to use git bisect

    By DanaServer Monitoring & Linux
    Share

    git bisect performs a binary search through your commit history to find the commit that first introduced a regression. You give it one commit you know is good and one you know is bad, and it walks you through the midpoints, halving the search space each time. With N commits between good and bad, it finds the culprit in roughly log₂(N) steps.

    This is the right tool when you know "it worked at some point and now it doesn't" but the change might be hundreds of commits ago.


    The basic flow

    git bisect start
    git bisect bad                # current commit is broken
    git bisect good v1.4.0        # this tag was fine
    
    # git checks out a commit halfway between
    # build / run your reproducer
    git bisect good               # or: git bisect bad
    
    # git checks out a new midpoint, repeat...
    # eventually:
    # abc1234 is the first bad commit
    git bisect reset              # return to where you were
    

    git bisect reset is mandatory at the end (or any time you want to abort). It puts your working tree back where it was before bisect started.


    Choosing your good and bad commits

    The good commit should be as recent as possible while still being free of the bug. The further apart good and bad are, the more steps bisect takes — and you'll have to test more commits.

    Useful sources for a known-good commit:

    • A release tag (v1.4.0)
    • A commit on main from before a feature was merged
    • The last commit you remember running successfully (find it in git log or your CI history)

    If you only know the bug appeared "recently", start with bad = HEAD and good = HEAD~50. Bisect at most 6 steps over 50 commits.


    Each step: the rhythm

    For every commit bisect checks out:

    1. Build the project (if needed).
    2. Run the reproducer — the smallest test that distinguishes good from bad.
    3. Tell git which it was:
      git bisect good
      git bisect bad
      

    Don't skip step 2 or trust your memory. Bisect's only job is to halve the search; you have to provide the good/bad signal.


    Skipping commits that can't be tested

    Some commits in the range may not even build (broken merges, half-finished work, missing dependencies). Skip them:

    git bisect skip
    

    Bisect picks a different midpoint nearby. If many adjacent commits skip, bisect may report a range of suspects instead of a single commit, and you'll have to investigate that range manually.

    To skip a range up front:

    git bisect skip <sha1>..<sha2>
    

    Automating bisect with a script

    If you can write a one-liner that returns 0 for good and non-zero for bad, you can hand the whole bisect to git:

    git bisect start HEAD v1.4.0
    git bisect run npm test
    

    Or with a custom script:

    git bisect run ./scripts/repro.sh
    

    The script's exit code is what bisect reads:

    • 0 — good
    • 1–124, 126–127 — bad
    • 125 — skip (can't be tested; common when build fails)
    • 128+ — abort the bisect

    A typical bisect script:

    #!/usr/bin/env bash
    set -e
    npm install --silent || exit 125    # build failure → skip
    node ./repro.js                      # 0 if bug NOT present, non-zero if present
    

    Note the inversion: repro.js should fail on the bad version, since bisect treats non-zero as bad.


    Practical recipes

    "It works on v1.4.0 but is broken on main"

    git bisect start
    git bisect bad main
    git bisect good v1.4.0
    # test, mark good/bad until done
    git bisect reset
    

    "Find when this test started failing"

    git bisect start HEAD v1.4.0
    git bisect run npm test -- --testPathPattern=login
    git bisect reset
    

    "Find when a function was renamed"

    You can bisect on anything testable, not just bugs. Reverse the meaning to find when something appeared:

    git bisect start HEAD v1.4.0
    git bisect run sh -c '! grep -q "newFunctionName" src/'
    # now "bad" = "newFunctionName exists" — bisect finds first appearance
    

    "I want a transcript of the bisect"

    git bisect log > bisect.log
    # share it with a colleague
    git bisect replay bisect.log
    

    git bisect replay lets someone else (or future-you) re-run the same bisect.


    Visualizing where you are

    git bisect visualize           # opens gitk if available
    git bisect visualize --oneline # shows remaining suspects
    

    Useful when you suspect bisect is converging on the wrong neighborhood and want to see the candidate set.


    After bisect identifies a commit

    Once bisect declares the first bad commit, do not assume the fix is "revert that commit". Inspect it:

    git show <bad-sha>
    git log --oneline <bad-sha>~5..<bad-sha>+5
    

    The commit may be:

    • The actual bug — fix or revert.
    • A correct change that exposed a latent bug elsewhere.
    • A merge commit — bisect reports the merge, not the underlying commit. Use git bisect start --first-parent if you want bisect to follow merges along the mainline only, or run a sub-bisect inside the merged branch.

    Etiquette and edge cases

    • Bisect rewrites your working tree on every step. Commit or stash uncommitted work before starting.
    • Bisect leaves a marker in .git/. A failed bisect that wasn't reset will block other operations. Always git bisect reset to clean up.
    • Submodules don't update automatically. If your project has submodules and the bug depends on submodule state, run git submodule update after each step (or in the bisect run script).
    • Be careful with database migrations. If your reproducer migrates the DB, bisect can leave you with an out-of-sync schema. Use a clean database per step.

    Summary

    • git bisect startbadgood, then mark each midpoint.
    • Use git bisect skip for unbuildable commits.
    • Use git bisect run <command> to fully automate when you have a deterministic test.
    • Always end with git bisect reset.
    • The first-bad commit isn't always the root cause — read it, don't just revert.