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 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 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 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 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 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 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
badinstead ofgoodat a single step made git blamee099160instead of the real culpritcbae02a. The cure isgit bisect log— save it, fix the wrong line, replay it withgit bisect replay. git bisect runreads 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
- Undo the last git commit but keep your changes
- Delete all merged local branches in git
- Remove a secret from git history
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.