Finding the First Bad Commit with Git Bisect
Learn how to use git bisect to pinpoint the exact commit that introduced a regression, with a concrete example, automation tips, and key limitations.
09 Jul 2026, 15:39 UTC

When a regression hides in hundreds of commits
You have a test suite that passes on version v1.0 but fails on the current HEAD. Manually checking each commit would take hours, especially if the project has a long history. Git bisect lets you locate the exact commit that introduced the failure by performing a binary search, reducing the number of builds you need to test from linear to logarithmic.
How git bisect works
The command maintains two endpoints: a known‑good commit where the bug is absent and a known‑bad commit where it is present. Git checks out a commit roughly halfway between them, you tell Git whether that commit is good or bad, and the range halves again. This continues until a single commit is identified as the first bad one. Internally Git stores the bisection state in .git/BISECT_* files, so you can safely interrupt the process and resume later as long as you run git bisect reset before starting a new bisect.
Worked example: locating a broken flag
Suppose a command‑line tool myapp gained a regression where the --verbose flag no longer prints extra output. You know that v1.0 (tag v1.0) works correctly and the current HEAD does not.
- Start the bisect, giving Git the bad and good endpoints:
- Git checks out a midpoint commit. Run your manual test:
- Repeat the test‑mark cycle. After each step Git reports roughly how many commits remain, e.g., "Bisecting: 3 revisions left to test after this (roughly 2 steps)".
- When Git finishes, it prints something like:
- Verify the finding by checking out that commit and its parent:
- Return to your original branch:
git bisect start HEAD v1.0
./myapp --verbose | grep "expected message"
If the message appears, the commit is good; otherwise it is bad. Mark the result:
# good case
git bisect good
# bad case
git bisect bad
Bisecting: 0 revisions left to test after this.
The first bad commit is:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
Author: ...
Date: ...
Add optional logging to --verbose
git checkout a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
./myapp --verbose | grep "expected message" # should fail
git checkout a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0^1
./myapp --verbose | grep "expected message" # should succeed
git bisect reset
Automating the search
If you can script the test, git bisect run eliminates manual marking. Create a script reproduce.sh that exits with code 0 when the bug is absent and any non‑zero code when it is present:
#!/usr/bin/env bash ./myapp --verbose | grep -q "expected message"Then run:
git bisect start HEAD v1.0 git bisect run ./reproduce.shGit will treat zero as good, non‑zero as bad, and automatically step through the commits. When it finishes, it reports the offending commit hash just as in the manual workflow.
Limitations and safety tips
- Monotonicity assumption: The bug must be present in every commit after it is introduced. Intermittent failures or flaky tests can cause Git to mark a good commit as bad (or vice‑versa), leading to an incorrect result.
- Skipping commits: If a commit cannot be built or tested, use
git bisect skip. Skipping too many commits may leave Git unable to pinpoint a single culprit, instead reporting a range of possible bad commits. - Rewritten history: Bisecting across a branch that has been force‑pushed or rebased can produce misleading results because the commit ancestry Git sees may not reflect the original timeline.
- Detached HEAD: During a bisect Git leaves you in a detached HEAD state. Always run
git bisect resetwhen you are done, even if you abort the process early, to avoid leaving the repository in an unexpected state.
Actionable closing checklist
- Identify a known‑good commit (e.g., a tag or release) and a known‑bad commit (often HEAD).
- Start the bisect:
git bisect start <bad> <good>. - Choose a testing method: manual verification for quick checks or a script that returns 0/non‑zero for automated runs.
- If manual, test each checked‑out commit and mark it with
git bisect goodorgit bisect bad. If automated, rungit bisect run ./your‑test.sh. - When Git reports a single first‑bad commit, verify it by checking out that commit and its parent.
- Run
git bisect resetto return to your original branch and clean up the bisection state.
By following these steps you can turn a potentially hour‑long manual hunt into a few minutes of focused testing, letting you keep development velocity high even when regressions appear deep in the history.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.