Finding the commit that broke a test with git bisect run: exit codes 0, 125 and 127
How git bisect binary-searches for the first bad commit, and how git bisect run reads exit codes: 0 is good, 1–124 and 126–127 are bad, 125 skips, anything else aborts. From Git's docs.
A test passed at the last release and fails now, with a few hundred commits in between. Checking them one by one isn’t realistic. git bisect narrows that range with a binary search and finds the commit that first introduced the problem. Hand git bisect run a script that decides, and you don’t even have to type good or bad yourself. But if you don’t know how Git reads the script’s exit code, it can name the wrong commit.
This post follows the git-bisect manual for Git 2.56.0 (September 28, 2026) and git-bisect-lk2009, the explanatory paper also published on git-scm.com.
The basic loop
You start by naming one commit that has the bug (bad) and one from before the bug appeared (good):
git bisect start
git bisect bad # Current version is bad
git bisect good v2.6.13-rc2 # v2.6.13-rc2 is known to be good
Git checks out a commit around the middle of that range and reports something like:
Bisecting: 675 revisions left to test after this (roughly 10 steps)
Build and test that version, then answer git bisect good or git bisect bad, and the remaining candidates roughly halve. About 676 candidates take about 10 steps because 2¹⁰ = 1024. When nothing is left to check, Git prints the first bad commit and leaves refs/bisect/bad pointing at it. Afterwards, git bisect reset takes you back to the HEAD you started from.
If history were a single line and the verdict flipped from good to bad exactly once, this would be the same problem as finding the first position in a sorted array where a condition becomes true. It’s the boundary that bisect_left finds, covered in Binary search insertion points.
How bisect run reads exit codes
git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good
git bisect run make # "make" builds the app
git bisect reset
git bisect run runs the command on each commit and classifies the commit by its exit code:
| Exit code | Result |
|---|---|
| 0 | good (old) |
| 1–127, except 125 | bad (new) |
| 125 | Commit can’t be tested; skipped |
| Anything else | The bisect itself is aborted |
This is where mistakes creep in. First, a program that ends with exit(-1) has its value chopped with & 0377, so the exit code is 255, and that aborts the run rather than marking the commit bad. Second, 126 and 127 are what POSIX shells use for “not executable” and “command not found”, but to bisect run they’re just ordinary failures in the 1–127 range. If your script ends with 127, that commit counts as bad. If it failed because a test tool didn’t exist yet in an old commit, Git takes that as the bug.
Commits that don’t even build should exit with 125. The official example:
#!/bin/sh
make || exit 125 # this skips broken builds
~/check_test_case.sh # does the test case pass?
The manual recommends keeping the test script outside the repository, and its examples use ~/. Bisect changes the working tree every time it switches commits, so a script tracked inside the repository may be missing or different in older commits.
When many commits are skipped
Skipping has a cost. If you skip a commit adjacent to the one you’re looking for, Git can’t tell exactly which of them was the first bad one. In that case it shows several candidate commits and stops.
What the binary search assumes
git-bisect-lk2009 notes that a commit graph can contain more than one first bad commit, and assumes a single one only while deriving its algorithm for choosing the best commit to split the range at. What matters in practice is that your good/bad test shows a usable transition from good to bad along the history being searched. If a bug appears, gets fixed and comes back, or shows up and disappears depending on the build output, the verdicts flip back and forth, and the boundary bisect reports can be unrelated to the cause or change from run to run. If you entered a wrong verdict, save git bisect log to a file, delete the wrong lines, then run git bisect reset followed by git bisect replay <file>.
A few options are worth knowing:
- When “good/bad” reads awkwardly, such as when looking for the commit that fixed a bug, run
git bisect startwith no arguments and mark commits withgit bisect oldandgit bisect new. --first-parentfollows only the first parent at merge commits. If a merged branch contained commits that didn’t build but the merge itself was fine, it names the merge commit and avoids that false positive.--reset-when-foundalso cleans up once the first bad commit is found. It’s in the 2.56.0 manual but not the 2.55.0 one, so checkgit --versionbefore relying on it.
The A..B notation for commit ranges is explained in Two dots or three in Git.
Documentation used
- git-bisect (2.56.0)
- git-bisect, 2.55.0 manual: compared for
--reset-when-found - git-bisect-lk2009: Fighting regressions with git bisect
Links and content were checked on October 5, 2026.