CLI AI

git bisect: find the commit that broke it

2026-07-20

git bisect start && git bisect bad && git bisect good <old-hash> starts a binary search through your history, and git bisect run ./check.sh finishes it without a single manual answer. The script decides by exit code: 0 is good, non-zero is bad, and 125 means "this commit can't be tested, skip it".

1. Mark the boundaries

clai
$ clai start looking for the commit that broke addition, known good is fa5873a→ git bisect start && git bisect bad && git bisect good fa5873aBisecting: 4 revisions left to test after this (roughly 2 steps)[37a14229ef73e1801a49defb16a86a26c122aa41] refactor step 5

bad with no argument means the current HEAD. Git immediately checks out the middle of the range and tells you how many steps are left: ten commits take three or four checks, a thousand take ten.

2. Answer at every step

clai
$ clai this commit works fine→ git bisect goodBisecting: 2 revisions left to test after this (roughly 1 step)[cbae02ada1b9e211691957bc7daff38a214a5a13] speed up addition

Test, answer good or bad, and git moves on. Every answer throws away half of what's left.

3. The answer

clai
$ clai this commit is broken→ git bisect badcbae02ada1b9e211691957bc7daff38a214a5a13 is the first bad commitcommit cbae02ada1b9e211691957bc7daff38a214a5a13Author: Demo <[email protected]>Date:   Thu Aug 27 22:52:21 2026 +0700    speed up addition calc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)

Git names the first bad commit and prints its diffstat right away. One git show <hash> later it turns out that "speed up addition" replaced a plus with a minus.

4. The same thing with zero manual answers

clai
$ clai find the breaking commit automatically, the test is ./check.sh→ git bisect run ./check.shrunning './check.sh'Bisecting: 0 revisions left to test after this (roughly 0 steps)[e0991601aa7cd6c25d3ac78b486319a7ee55713e] refactor step 6running './check.sh'cbae02ada1b9e211691957bc7daff38a214a5a13 is the first bad commitbisect found first bad commit

This is the part worth remembering. The script returns 0 for a good commit and anything non-zero for a bad one, and it can be a single line: test "$(./calc.sh 2 3)" = "5".

5. Commits that don't even build

clai
$ clai skip commits that fail to build→ git bisect run ./check125.sh

Exit code 125 is special: it means "this commit can't be judged, skip it". Such a step shows up in git bisect log as a # skip: line. The usual shape is: build failed → exit 125, test failed → exit 1.

6. Get back to where you started

clai
$ clai finish the bisect and return to my branch→ git bisect resetPrevious HEAD position was cbae02a speed up additionSwitched to branch 'main'

During a bisect the repository sits in detached HEAD. reset puts you back on the branch you started from. Forget it and you'll spend the evening wondering why your working copy is full of old code.

Gotchas

  • One wrong answer ruins the whole run. Tested on a live repository: answering bad instead of good at a single step made git blame e099160 instead of the real culprit cbae02a. The cure is git bisect log — save it, fix the wrong line, replay it with git bisect replay.
  • git bisect run reads exit codes, not output. A script that prints "FAILED" and exits 0 counts as success. Anything above 127 aborts the bisect entirely.
  • Keep the test script outside the repository. An untracked file survives the checkouts, but a script that exists only in recent commits will vanish the moment git checks out an older one — taking your bisect with it.

Related questions

What if I don't know a good commit? Take one that is obviously old: git bisect good HEAD~200. If it turns out to be broken too, git says so on the first step.

Can I bisect on a string in the output? Yes, with any command: git bisect run sh -c '! ./app 2>&1 | grep -q "NullPointer"'. All that matters is that 0 means good.

How do I see what the guilty commit changed? git show <hash> prints the full diff, git show --stat <hash> just the list of files.

See also

CliAI writes the bisect commands and the one-line test script from a plain description, and shows both before anything runs. Install it in one line.